-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Combine request context with parent receiver context (#733)
PR #718 changed the handler to always receive the request context in order to support middleware that alters the context. This however means we lose any context values (loggers, etc) that are set on the outer context. This change introduces a delegating context that will prioritize the child (request) context for values and cancellation, but fallback to the parent context for missing values. Signed-off-by: Ben Moss <benm@vmware.com>
- Loading branch information
Showing
4 changed files
with
148 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package context | ||
|
||
import "context" | ||
|
||
type valuesDelegating struct { | ||
context.Context | ||
parent context.Context | ||
} | ||
|
||
// ValuesDelegating wraps a child and parent context. It will perform Value() | ||
// lookups first on the child, and then fall back to the child. All other calls | ||
// go solely to the child context. | ||
func ValuesDelegating(child, parent context.Context) context.Context { | ||
return &valuesDelegating{ | ||
Context: child, | ||
parent: parent, | ||
} | ||
} | ||
|
||
func (c *valuesDelegating) Value(key interface{}) interface{} { | ||
if val := c.Context.Value(key); val != nil { | ||
return val | ||
} | ||
return c.parent.Value(key) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package context | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
) | ||
|
||
func TestValuesDelegating(t *testing.T) { | ||
type key string | ||
tests := []struct { | ||
name string | ||
child context.Context | ||
parent context.Context | ||
assert func(*testing.T, context.Context) | ||
}{ | ||
{ | ||
name: "it delegates to child first", | ||
child: context.WithValue(context.Background(), key("foo"), "foo"), | ||
parent: context.WithValue(context.Background(), key("foo"), "bar"), | ||
assert: func(t *testing.T, c context.Context) { | ||
if v := c.Value(key("foo")); v != "foo" { | ||
t.Errorf("expected child value, got %s", v) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "it delegates to parent if missing from child", | ||
child: context.Background(), | ||
parent: context.WithValue(context.Background(), key("foo"), "foo"), | ||
assert: func(t *testing.T, c context.Context) { | ||
if v := c.Value(key("foo")); v != "foo" { | ||
t.Errorf("expected parent value, got %s", v) | ||
} | ||
}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got := ValuesDelegating(tt.child, tt.parent) | ||
tt.assert(t, got) | ||
}) | ||
} | ||
} | ||
func TestValuesDelegatingDelegatesOtherwiseToChild(t *testing.T) { | ||
parent, parentCancel := context.WithCancel(context.Background()) | ||
child, childCancel := context.WithCancel(context.Background()) | ||
derived := ValuesDelegating(child, parent) | ||
|
||
ch := make(chan string) | ||
go func() { | ||
<-derived.Done() | ||
ch <- "derived" | ||
}() | ||
go func() { | ||
<-child.Done() | ||
ch <- "child" | ||
}() | ||
go func() { | ||
<-parent.Done() | ||
ch <- "parent" | ||
}() | ||
|
||
parentCancel() | ||
v1 := <-ch | ||
if v1 != "parent" { | ||
t.Errorf("cancelling parent should not cancel child or derived: %s", v1) | ||
} | ||
childCancel() | ||
v2 := <-ch | ||
v3 := <-ch | ||
diff := cmp.Diff([]string{"derived", "child"}, []string{v2, v3}, cmpopts.SortSlices(func(a, b string) bool { return a < b })) | ||
if diff != "" { | ||
t.Errorf("unexpected (-want, +got) = %v", diff) | ||
} | ||
} |