Ramblings of an aging IT geek
← Ramblings of an aging IT geek
golang

stop reaching for context.Background() in the middle of things

Why threading a real context.Context down through your call stack instead of conjuring a fresh Background() one is the difference between cancellation working and your goroutines outliving the request.

A terminal showing Go source on screen

The thing nobody tells you about context.Context is that the hard part isn't understanding it, it's resisting the urge to short-circuit it. You know the rule: pass it as the first argument, name it ctx, never store it in a struct. Easy. Then you're three layers deep in some helper that needs to make an HTTP call, you don't have a ctx in scope, and there it is, the seductive context.Background(), right where you need it.

Don't. The moment you call context.Background() halfway down the stack, you've cut the wire. The deadline from the inbound request, the cancellation when the client hangs up, the trace span, all of it stops at that line. Your shiny new background context knows nothing and will happily let that goroutine run until the heat death of the universe, or at least until the connection pool is exhausted, which arrives sooner.

The fix is dull and it is the right one: thread the real context through. If the helper needs a ctx, give the helper a ctx parameter, even if that means editing six function signatures to do it. Yes, the diff is ugly. Yes, it feels like make-work. But cancellation only works if the chain is unbroken from the top of the request to the bottom, and the one place it's broken is the one place it'll leak.

I now treat context.Background() and context.TODO() as smells anywhere except main, a test, or the top of a long-lived worker. grep -n 'context.Background()' in the middle of a package is usually me finding a bug I haven't hit yet.