Using Go Context Cancellation: Patterns, Best Practices, and Common Pitfalls
This article explains why and how to use Go's context cancellation feature, covering listening for cancel events, emitting cancellations, time‑based timeouts, practical code examples, and important pitfalls to write faster and more robust backend services.
This article explains how to leverage Go's context cancellation feature and presents patterns and best practices that make your programs faster and more robust.
Why cancellation? When a client aborts a request, downstream work such as database queries or HTTP calls can continue unnecessarily, wasting resources. Proper cancellation stops all downstream components as soon as the original request is known to be finished.
Listening for cancel events – a context provides a Done() channel that is closed when cancellation occurs. The pattern is to select on <-ctx.Done() alongside other work. Example:
func main() {
http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
fmt.Fprint(os.Stdout, "processing request\n")
select {
case <-time.After(2 * time.Second):
w.Write([]byte("request processed"))
case <-ctx.Done():
fmt.Fprint(os.Stderr, "request cancelled\n")
}
}))
}Running the server and closing the browser before two seconds triggers the cancellation branch, printing “request cancelled”.
Submitting cancel events – use context.WithCancel to obtain a cancel function that can be called when an operation fails. Example with two dependent operations:
func operation1(ctx context.Context) error {
time.Sleep(100 * time.Millisecond)
return errors.New("failed")
}
func operation2(ctx context.Context) {
select {
case <-time.After(500 * time.Millisecond):
fmt.Println("done")
case <-ctx.Done():
fmt.Println("halted operation2")
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
go func() {
if err := operation1(ctx); err != nil {
cancel()
}
}()
operation2(ctx)
}If operation1 returns an error, cancel() stops operation2 promptly.
Time‑based cancellation – context.WithTimeout or context.WithDeadline automatically cancel after a duration or at a specific time. Example of an HTTP request with a 100 ms timeout:
func main() {
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)
req = req.WithContext(ctx)
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
fmt.Println("Request failed:", err)
return
}
fmt.Println("Response received, status code:", res.StatusCode)
}The request either succeeds within the deadline or fails with a “context deadline exceeded” error.
Traps and warnings – a context can be cancelled only once, so it should not be used to signal multiple errors. Pass the same context to every function that may need cancellation, and avoid wrapping an already cancellable context with another WithTimeout or WithCancel , which can lead to confusing behaviour.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.