Nestor Sokil
Nestor Sokil

Reputation: 2272

Context without cancel propagation

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

Answers (3)

Peter
Peter

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

CAFxX
CAFxX

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 no Deadline or Err, and its Done channel is nil. Calling Cause on the returned context returns nil.

Upvotes: 13

Volker
Volker

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

Related Questions