Reputation: 2272
How can I create a copy (a clone if you will) of a Go context that contains all of the values stored in the original, but does not get canceled when the original does?
It does seem like a valid use case to me. Say I have an http request and its context is canceled after the response is returned to a client and I need to run an async task in the end of this request in a separate goroutine that will most likely outlive the parent context.
func Handler(ctx context.Context) (interface{}, error) {
result := doStuff(ctx)
newContext := howDoICloneYou(ctx)
go func() {
doSomethingElse(newContext)
}()
return result
}
Can anyone advice how this is supposed to be done?
Of course I can keep track of all the values that may be put into the context, create a new background ctx and then just iterate through every possible value and copy... But that seems tedious and is hard to manage in a large codebase.
Upvotes: 31
Views: 15789
Reputation: 31720
Update: Go 1.21 added WithoutCancel to the context package.
Since context.Context is an interface, you can simply create your own implementation that is never canceled:
import (
"context"
"time"
)
type noCancel struct {
ctx context.Context
}
func (c noCancel) Deadline() (time.Time, bool) { return time.Time{}, false }
func (c noCancel) Done() <-chan struct{} { return nil }
func (c noCancel) Err() error { return nil }
func (c noCancel) Value(key interface{}) interface{} { return c.ctx.Value(key) }
// WithoutCancel returns a context that is never canceled.
func WithoutCancel(ctx context.Context) context.Context {
return noCancel{ctx: ctx}
}
Upvotes: 33
Reputation: 30331
Starting in go 1.21 this functionality is available directly in the standard library via context.WithoutCancel
:
func WithoutCancel(parent Context) Context
WithoutCancel
returns a copy of parent that is not canceled when parent is canceled. The returned context returns noDeadline
orErr
, and itsDone
channel isnil
. CallingCause
on the returned context returnsnil
.
Upvotes: 13
Reputation: 42431
Can anyone advice how this is supposed to be done?
Yes. Don't do it.
If you need a different context, e.g. for your asynchronous background task then create a new context. Your incoming context and the one of your background task are unrelated and thus you must not try to reuse the incoming one.
If the unrelated new context needs some data from the original: Copy what you need and add what's new.
Upvotes: 10