Clean code: production contracts · 3 MIN READ

Context is a cancellation contract, not a bag of dependencies

Propagate request cancellation while giving detached work an explicit owner and deadline.

Passing context.Context as the first parameter lets cancellation and deadlines cross a call boundary. It does not force the callee to stop, and it does not prove that a goroutine has exited. Context values are for request-scoped metadata that must cross API boundaries, not an alternative constructor or a hidden transport for required service dependencies.

clean-code-production · A MAP OF THE IDEA

  1. Request context
  2. Bounded dependency
  3. Cancellation signal
  4. Owner joins or queues

Useful when

  • Propagate request cancellation while giving detached work an explicit owner and deadline.
  • Makes the owner and the failure boundary explicit.

Watch out

  • Storing a request context in a long-lived struct can retain request data and cancel unrelated work.
  • Run native service and failure tests in your own development environment.

Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.

01

The contract and the failure

Passing context.Context as the first parameter lets cancellation and deadlines cross a call boundary. It does not force the callee to stop, and it does not prove that a goroutine has exited. Context values are for request-scoped metadata that must cross API boundaries, not an alternative constructor or a hidden transport for required service dependencies.

02

Work through the example

Pass the request context into storage and outgoing calls. Check cancellation at meaningful handoffs and stop producers as well as consumers. For must-complete work, prefer a durable job or an application-owned worker with an explicit shutdown budget. Keep tenant/principal values typed and verified; an arbitrary value in context does not establish authorization.

go
package main

import (
	"context"
	"errors"
	"fmt"
)

func main() {
	ctx, cancel := context.WithCancelCause(context.Background())
	cancel(errors.New("request no longer needed"))
	fmt.Println(ctx.Err())          // context canceled
	fmt.Println(context.Cause(ctx)) // request no longer needed
	detached := context.WithoutCancel(ctx)
	fmt.Println(detached.Err(), detached.Done() == nil) // <nil> true
}
Read the code

Err identifies cancellation broadly; Cause retains the chosen explanation. WithoutCancel removes the parent cancellation, deadline and Done signal, so it must not be used as a convenient way to make an unowned goroutine live forever. A background owner must supply its own bounded context and join or durably queue the work.

03

Production decisions

Storing a request context in a long-lived struct can retain request data and cancel unrelated work. Replacing every context with Background hides client cancellation. Calling cancel alone does not join a worker. Test cancellation while waiting, during an operation and after the caller stops reading results. Explain which side owns the result channel and how both sides exit.

04

Explain it in an interview

Does context.WithoutCancel preserve the request deadline? No. It removes cancellation and deadline propagation; detached work needs a separately owned context, budget and completion or recovery policy. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.

TAKE THIS WITH YOU

Propagate request cancellation while giving detached work an explicit owner and deadline. No. It removes cancellation and deadline propagation; detached work needs a separately owned context, budget and completion or recovery policy.

Check your understanding

Does context.WithoutCancel preserve the request deadline?

Go to the source

Progress stays in this browser. No account needed.