Write a function that runs a blocking call with a context and returns early on cancellation. What goroutine leak must you avoid?
Question 280HardGo 1.22 to 1.25
A common pattern wraps a legacy blocking function in a goroutine and uses select to wait for either the result or ctx.Done(). The trap: if the result channel is unbuffered and the context wins, the worker goroutine blocks forever trying to send, which is a leak on every timeout.
func withContext[T any](ctx context.Context, fn func() (T, error)) (T, error) {
type result struct {
v T
err error
}
ch := make(chan result, 1) // buffered: the sender never blocks
go func() {
v, err := fn()
ch <- result{v, err}
}()
select {
case r := <-ch:
return r.v, r.err
case <-ctx.Done():
var zero T
return zero, context.Cause(ctx)
}
}
What to say in the interview:
- This abandons the work; it does not stop it.
fnkeeps running and keeps holding resources. The real fix is to makefnaccept a context. If that isn't possible, bound how many such goroutines can exist. - If the result owns resources (a file, a connection), the abandoned result must be cleaned up, for example by a goroutine that drains
chand closes it. - The buffer size of 1 is the key detail interviewers look for.
goleakin tests catches this class of bug.
More on Context
- 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?
- Q281How should a long-running worker loop respect cancellation? Discuss select fairness and CPU-bound loops.
- Q282How does errgroup.WithContext use context, and what is a common bug with it?
- Q283How would you merge two contexts so the result is canceled when either one is?
- Q284Can you implement your own Context type? How does the stdlib propagate cancellation from a custom parent?