When adding or modifying concurrent code, treat context/cancellation, goroutine lifecycle, and state ownership as first-class requirements.
Practical standards: 1) Always pass the caller’s ctx down the call chain
2) Never block forever on goroutine completion
wg.Wait() or “wait for completion” that can’t be released when ctx is cancelled.select loops and/or separate completion channels that can be selected against ctx.Done().3) Don’t share mutable variables between goroutines
err/results, send them over a chan (or use a thread-safe primitive). Avoid capturing a local variable and writing to it concurrently.4) Make teardown cancellation effective
context.Background(), so injected delays/faults terminate on cleanup.5) Ensure async goroutines don’t outlive tests/owner scopes
<-done). Avoid “log after test complete” races.6) State handovers must have clear ownership and ordering
7) Test isolation under concurrency
Illustrative patterns:
A) Cancel-aware goroutine completion without shared err
errCh := make(chan error, 1)
go func() {
// compute / call that may block
errCh <- runtimeService.StopContainer(ctx, id, grace)
}()
select {
case <-ctx.Done():
// caller cancelled; return quickly
return ctx.Err()
case err := <-errCh:
return err
}
B) Goroutine leak prevention in tests
done := make(chan struct{})
go func() {
defer close(done)
_ = obj.Run(ctx, sourcesReady)
}()
// ... trigger stop
cancel() // or close(stopCh)
<-done // wait until Run actually exits
C) Don’t hide non-cancellable waits