EDIT 2023-02-05: Last try: #36503 (comment).
EDIT 2023-01-18: Updated proposal with 2 alternatives: #36503 (comment).
EDIT 2023-01-06: Updated proposal: #36503 (comment).
EDIT 2020-07-01: The proposal was amended to split cancellation and values concerns: #36503 (comment).
( This proposal is alternative to #36448. It proposes to add context.Merge
instead of exposing general context
API for linking-up third-party contexts into parent-children tree for efficiency )
Current context
package API provides primitives to derive new contexts from one parent - WithCancel
, WithDeadline
and WithValue
. This functionality covers many practical needs, but not merging - the case where it is neccessary to derive new context from multiple parents. While it is possible to implement merge functionality in third-party library (ex. lab.nexedi.com/kirr/go123/xcontext), with current state of context
package, such implementations are inefficient as they need to spawn extra goroutine to propagate cancellation from parents to child.
To solve the inefficiency I propose to add Merge
functionality to context
package. The other possibility would be to expose general mechanism to glue arbitrary third-party contexts into context tree. However since a) Merge
is a well-defined concept, and b) there are (currently) no other well-known cases where third-party context would need to allocate its own done
channel (see #28728; this is the case where extra goroutine for cancel propagation needs to be currently spawned), I tend to think that it makes more sense to add Merge
support to context
package directly instead of exposing a general mechanism for gluing arbitrary third-party contexts.
Below is description of the proposed API and rationale:
---- 8< ----
Merging contextsMerge could be handy in situations where spawned job needs to be canceled whenever any of 2 contexts becomes done. This frequently arises with service methods that accept context as argument, and the service itself, on another control line, could be instructed to become non-operational. For example:
func (srv *Service) DoSomething(ctx context.Context) (err error) { defer xerr.Contextf(&err, "%s: do something", srv) // srv.serveCtx is context that becomes canceled when srv is // instructed to stop providing service. origCtx := ctx ctx, cancel := xcontext.Merge(ctx, srv.serveCtx) defer cancel() err = srv.doJob(ctx) if err != nil { if ctx.Err() != nil && origCtx.Err() == nil { // error due to service shutdown err = ErrServiceDown } return err } ... }func Merge
func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFunc)
Merge merges 2 contexts into 1.
The result context:
Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.
---- 8< ----
To do the merging of ctx
and srv.serveCtx
done channels current implementation has to allocate its own done channel and spawn corresponding goroutine:
https://lab.nexedi.com/kirr/go123/blob/5667f43e/xcontext/xcontext.go#L90-118
https://lab.nexedi.com/kirr/go123/blob/5667f43e/xcontext/xcontext.go#L135-150
context.WithCancel
, when called on resulting merged context, will have to spawn its own propagation goroutine too.
For the reference here is context.Merge
implementation in Pygolang that does parents - child binding via just data:
https://lab.nexedi.com/kirr/pygolang/blob/64765688/golang/context.cpp#L74-76
https://lab.nexedi.com/kirr/pygolang/blob/64765688/golang/context.cpp#L347-352
https://lab.nexedi.com/kirr/pygolang/blob/64765688/golang/context.cpp#L247-251
https://lab.nexedi.com/kirr/pygolang/blob/64765688/golang/context.cpp#L196-226
bcmills, muhlemmer, cretz, OneOfOne, hbagdi and 42 moreas
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4