# Propagating Cancellation — Go Concurrency Patterns

Source: https://www.skillbyai.com/en/go-concurrency/gc-ctx-propagate

> Passing ctx down and honouring it in goroutines.

## Pass it down, check it in loops

Cancellation is **cooperative**: a context cannot stop a goroutine by force. Every function that does I/O or may block should accept a `ctx` and pass it to whatever it calls, for example `http.NewRequestWithContext(ctx, ...)` or `db.QueryContext(ctx, ...)`, so that a single cancel at the top stops the whole tree of work. A goroutine that loops must check `ctx.Done()` in its `select`, both when waiting for input and when **sending** output, because a send to a consumer that has gone away would otherwise block forever. Long CPU-bound loops can check `ctx.Err() != nil` every so many iterations. In HTTP servers, `r.Context()` is cancelled when the client disconnects or the handler returns, so derive from it rather than from Background.

## A producer that respects cancellation on send

Both the wait and the send are guarded by ctx.Done().

```go
package main

import (
	"context"
	"fmt"
)

func numbers(ctx context.Context) <-chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		for i := 0; ; i++ {
			select {
			case out <- i:
			case <-ctx.Done():
				return // consumer is gone: exit instead of blocking forever
			}
		}
	}()
	return out
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	for n := range numbers(ctx) {
		if n == 3 {
			break
		}
		fmt.Println(n)
	}
	cancel() // stops the producer goroutine; defer would also do it
}
```

## Do not replace a request context with Background

Calling `context.Background()` deep inside a request handler cuts the cancellation chain: the work keeps running after the client has gone. If work must outlive the request, make that explicit (for example with `context.WithoutCancel` in Go 1.21+) and give it its own timeout.

**Quiz:** Why must a producer goroutine also select on ctx.Done() when sending?

- [ ] Channels are closed automatically on cancellation
- [ ] Sending is not allowed without a context
- [ ] ctx.Done() makes the send faster
- [x] If the consumer stops receiving, the send would block forever and leak the goroutine

*Answer:* If the consumer stops receiving, the send would block forever and leak the goroutine. Cancellation is cooperative; every blocking point must be able to observe it.
