AttachContext is not transitive
rantav opened this issue · 5 comments
I realized there is another problem with AttachContext and friends.
Attaching a context and then creating a new context and attaching it, makes RequestCtx
forget the previous attached context, which to me seems like a breach of the context contract
Example test:
func Test_ValueTransitive(t *testing.T) {
ctx := acquireRequestCtx(new(fasthttp.RequestCtx))
ctx2 := context.WithValue(ctx, "k2", "v2")
ctx.AttachContext(ctx2)
if v := ctx.Value("k2"); v != "v2" {
t.Errorf("Value() key '%s' == %v, want %v", "k2", v, "v2")
}
ctx3 := context.WithValue(ctx, "k3", "v3")
ctx.AttachContext(ctx3)
// Should remember *both* k2 and k3
if v := ctx.Value("k2"); v != "v2" {
t.Errorf("Value() key '%s' == %v, want %v", "k2", v, "v2") // THIS FAILS, but should not
}
if v := ctx.Value("k3"); v != "v3" {
t.Errorf("Value() key '%s' == %v, want %v", "k3", v, "v3")
}
}
I'll send a pull request with fix suggestion soon
Hi @rantav,
If you create a second context with value, you must use ctx.AttachedCtx()
not the RequestCtx
again, because fasthttp doesn't support overwrite the original RequestCtx
. Atreugo only adds an extra layer to support attached context.
So must do:
// First time
ctx2 := context.WithValue(ctx, "k2", "v2")
ctx.AttachContext(ctx2)
// Second time or more
ctx3 := context.WithValue(ctx.AttachedContext(), "k3", "v3")
ctx.AttachContext(ctx3)
Thanks @savsgio I understand your point. Let me try to add another angle if I may.
Two points I think needs to be considered.
- It's easy to miss the "use
ctx.AttachedContext()
instead of ctx" instruction. There's no compiler help and by convention everyone using acontext.Context
are already wrapping one context inside another so that would be their first instinct, to simply usectx
to attach a new value. So in terms of API ergonomics this could be a potential pitfall. - What if I attach a value to ctx and I also attach another value to actx. Now I want to add a third value, preserving the last two? a) I can't do that and b) there's a surprise element. Example:
ctx := acquireRequestCtx(new(fasthttp.RequestCtx))
ctx.SetUserValue("k1", "v1") // ctx contains k1
ctx2 := context.WithValue(ctx, "k2", "v2")
ctx.AttachContext(ctx2) // ctx contains k1, k2
// I want ctx3 to wrap both k1 and k2, but I can't.
// Option 1
ctx3 := context.WithValue(ctx, "k3", "v3") // ctx3 contains k3, k1 and k2
// but if we do that: surprise
ctx.AttachContext(ctx3) // now ctx contains k1, k3 (not k2)
// Option 2 (instead of 1)
ctx3 := context.WithValue(ctx2, "k3", "v3") // ctx3 contains k3, k2
// And now, surprise
ctx.AttachContext(ctx3) // now ctx contains k1, k3 (not k2)
I'm sorry, but your example is good, the ctx
has the 3 keys:
fmt.Println(ctx.Value("k1"), ctx.Value("k2"), ctx.Value("k3"))
# Print: v1 v2 v3
Keep in mind, that you could only attach one context, if you attach another, you override the previous, that's why to use the ctx.AttachedContext()
to keep the previously.
About use always the ctx
and not the ctx.AttachedContext()
, fasthttp and atreugo only implement the interface of context.Context
to use as a simple context but not as a context with values, because it's generate some garbage and reduce the performance significally.
Maybe in a future, @erikdubbelboer or @kirillDanshin will implement it, but now, it's not possible.
ok, I get it, it's a limitation or "by design" of fasthttp.
Sorry, use attached context carefully.
I could't say you anything more!