Urgent.News

One page, thousands of outlets. See who else covered it.

Editions

Tech

The context you passed is not the context that cancels

Two failure modes, opposite in effect, same root cause. Both compile, and both are quiet in development. Failure one: the work dies with the request func ( h * Handler ) Create ( w http . ResponseWriter , r * http . Request ) { order := parse ( r ) save ( order ) go func () { h . sendConfirmationEmail ( r . Context (), order ) // cancelled the moment Create returns }() w . WriteHeader ( http .…

Two types of failure modes, seemingly opposite yet stemming from the same root cause, can lead to issues in Go applications. Both compile silently during development. In the first case, the work dies when the request handler returns, causing the context to be cancelled. The goroutine then encounters an already dead context, resulting in unsuccessful email sending.

The fix introduced in Go 1.21 addresses this issue by using `context.WithoutCancel(r.Context())`, which preserves valuable information like trace IDs, request-scoped logger, and authentication subject, while detaching from the parent's cancellation. To handle timeout, a new context can be created using `context.WithTimeout()`.

In the second failure mode, cancellation never arrives, causing a loop to complete against a client that has already hung up, resulting in a database connection being held for an extended period. This issue is exacerbated under load, leading to connection pool exhaustion. The problematic code is when the caller cancels but does not properly handle the cancellation, causing the loop to continue executing.

While `go vet` can detect lost cancels, it doesn't catch lost contexts. To address this, the `contextcheck` lint rule identifies functions that receive a context but then pass `context.Background()` downstream, which is a major issue. Implementing tests that assert cancellation propagation and pre-cancelling before calling functions also helps mitigate this problem.

A key principle to remember is that a context is tied to the lifetime of an operation. When the operation's lifetime changes, the context must adapt accordingly. In practice, this means using `context.WithoutCancel()` for work that outlives the request, along with a fresh timeout, and passing the context down unchanged to every function that accepts one.

In Go applications, the majority of `context.Background()` calls should be located at the main entry point, tests, and minimal other locations. The recurring presence of `context.Background()` throughout the codebase often serves as an indication of a potential failure mode.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Incident with Github.com

Article URL: https://www.githubstatus.com/incidents/zkxwbgr0cnmx Comments URL: https://news.ycombinator.com/item?id=49330684 Points: 520 # Comments: 411

More from Monday 17 August →