Canceling a context

April 28, 2025

I’m jumping out of order a bit today, to cover a more basic function first, before the more advanced version. Bear with me.

func WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

WithCancel returns a derived context that points to the parent context but has a new Done channel. The returned context’s Done channel is closed when the returned cancel function is called or when the parent context’s Done channel is closed, whichever happens first.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

Example

WithCancel is the most basic way to extend a context. So it’s a good place to start.

It accepts an existing context, but that may be a background context (i.e. the result of calling context.Background()). It returns a new derivitive context, and a cancel function.

Here’s what it looks like in use:

func doTwoThings(ctx context.Context) {
	ctx, cancel = context.WithCancel(ctx)
	defer cancel

	go func() {
		if err := thingOne(ctx); err !=nil {
			log.Printf("thing one failed: %s", err)
			cancel()
		}
	}()

	go func() {
		if err := thingTwo(ctx); err != nil {
			log.Printf("thing two failed: %w", err)
			cancel()
		}
	}()
}

In this example, we start two goroutines, each listening for cancelation on the same context. Then if either function (thingOne or thingTwo) returns an error, the context is canceled, indicating to the other goroutine that it can abort its work, if it’s still not finished.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Context cancelation

As already mentioned, the context.Context type serves two purposes: It manages cancelation signals, and it allows passing request-scoped data. In my opinion, these two purposes are distinct enough that they probably should be handled by two separate mechanisms. But alas, that’s not what we have. However, I think it’s still useful to think of the two uses as distinct. Most often (at least in a well-designed application), the primary purpose of a context is the propegation of cancelation signals.


The Context API contract

Today we come to the core of the context package: The Context interface itself. type Context type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } A Context carries a deadline, a cancellation signal, and other values across API boundaries. Context’s methods may be called by multiple goroutines simultaneously. For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.


Contexts with timeouts

func WithTimeout func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete: func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation completes before timeout elapses return slowOperation(ctx) } Not only is WithTimeout conceptually similar to WithDeadline, it literally is WithDeadline, as we can see form the source code:

Get daily content like this in your inbox!

Subscribe