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()returnsnilandErr()returnsnil.Deadline()returns the zero time withok == false.context.Causeon it returnsnil.
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
- Q273What is context.AfterFunc and what are the semantics of its stop function?
- Q274How do you make a blocking call that doesn't accept a context (e.g. net.Conn.Read) cancelable?
- Q276What is wrong with this handler? What error will the goroutine see?
- Q277When exactly is an http.Request's context canceled on the server side, and how do you use it correctly?
- Q278On the HTTP client side, how does context interact with http.Client.Timeout and response bodies?
- Q279How does context cancellation work with database/sql, including transactions and rows?