Go

What does context.WithoutCancel do and when would you use it?

Question 275HardGo 1.22 to 1.25

WithoutCancel(parent) (Go 1.21) returns a context that keeps the parent's values but is never canceled:

  • Done() returns nil and Err() returns nil.
  • Deadline() returns the zero time with ok == false.
  • context.Cause on it returns nil.

It separates "request identity" (trace ID, auth, logger) from "request lifetime". Use it for work that must finish even after the triggering request ends: audit logs, cache writes after a response, outbox publishing, and cleanup or rollback after a timeout.

func (h *Handler) Checkout(w http.ResponseWriter, r *http.Request) {
	order, err := h.svc.PlaceOrder(r.Context(), r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	// Detach from the request lifetime but keep the trace ID; add a new budget.
	bg, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 5*time.Second)
	go func() {
		defer cancel()
		if err := h.events.Publish(bg, order); err != nil {
			slog.ErrorContext(bg, "publish failed", "err", err)
		}
	}()
	w.WriteHeader(http.StatusCreated)
}

Gotchas: always add a new timeout, or the detached work can run forever. It also keeps the parent's values (and anything they reference) alive for as long as it lives. Before Go 1.21, people wrote their own "detached" context type that forwarded only Value.

More on Context

All 35 Context questions