Description
What
I propose to add a WithoutCancel
function in context
that, given a parent Context
returns a new child Context
with the same values of the parent, but that is not canceled when the parent is canceled. If needed, although it seems not entirely warranted given the widespread use of similar functions (see section below), this new function could initially live in x/net/context
and be migrated to context
later.
In addition to the above, we should clarify a point that is only laid out implicitly in the context
API docs, i.e., that values attached to the Context
can also be used after the Context
has been canceled.
https://go.dev/cl/459016 contains the proposed implementation.
Why
This is useful in multiple frequently recurring and important scenarios:
- handling of rollback/cleanup operations in the context of an event (e.g., HTTP request) that has to continue regardless of whether the triggering event is canceled (e.g., due to timeout or the client going away)
- handling of long-running operations triggered by an event (e.g., HTTP request) that terminates before the termination of the long-running operation
This is doable today by not propagating the triggering event's context and replacing it instead with a new context obtained from context.Background()
. This is problematic though, as the new context does not contain any of the values attached to the context of the triggering event, and these values are important to e.g., ensure correct authentication/logging/tracing/error recovery functionality (a common scenario when using a middleware-based approach to request/event handling).
As noted below with @davecheney, a nice consequence that naturally falls out of this approach is that it effectively turns cancellation into a regular context value that can be overridden in children contexts. It doesn't solve the problem of cancellation being conflated with the intent of context being just a "bag of values" (that would almost certainly require breaking changes to solve) but it's an effective step into alleviating the situation, and it's backward compatible.
As noted further below with @martisch the benefit of this approach is that it's pretty much as minimal, composable, and in line with the current design of the context
package as possible, requiring a single new public API and eschewing conflating additional mechanisms (goroutines).
An important point to be made is that all existing implementations of this (see below) rely on an internal/undocumented guarantee of cancelCtx
. If this proposal is shot down at least that guarantee should be explicitly documented in the exported API.
Existing implementations
Looking around it is possible to find multiple reimplementations of this proposal, almost identical but with different names. I'm not advocating for a specific name here.
- An implementation already exists here: https://godoc.org/github.com/golang/tools/internal/xcontext#Detach (technically not optimal, since
Value()
can be implemented by embedding the parentContext
) - this implementation is also duplicated at https://godoc.org/golang.org/x/pkgsite/internal/xcontext#Detach - Just internally to our company I counted 3 similar implementations (the "canonical" one is called
contextutil.WithoutCancellation
, with the package name chosen only to avoid clashes with thecontext
package from the standard library) - @martisch mentioned below seeing a
detach.FromContext
implementation - Uber has a
NewDisconnectedContext
- AWS has
DetachContext
- teivah/onecontext has
Detach
- A search on sourcegraph reveals 40+ copies of
Detach
(this likely grossly underestimates how common this is used in the wild, as we only search functions calledDetach
) - I expect other non-public codebases elsewhere to contain additional implementations. Please let me know if you're aware of additional ones.
Implementations are trivial and would add a single public function to context
.