Files
go-kata/17-context-aware-channel-sender/README.md
2026-01-04 20:40:07 +01:00

57 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kata 17: The Context-Aware Channel Sender (No Leaked Producers)
**Target Idioms:** Pipeline Cancellation, Select-on-Send, Channel Ownership, Goroutine Leak Prevention
**Difficulty:** 🟡 Intermediate
## 🧠 The "Why"
A goroutine sending on a channel blocks until a receiver is ready (unless buffered space is available). If the receiver exits early (timeout, HTTP cancel, upstream error), producers can block forever and leak.
Idiomatic Go fixes this by:
- threading `context.Context` through the pipeline
- **selecting on every send** (`case out <- v` vs `case <-ctx.Done()`), as recommended in Gos pipeline cancellation patterns and real leak writeups.
## 🎯 The Scenario
Youre building a data pipeline step that fetches N URLs concurrently and streams results downstream. If the request is canceled (client disconnect, global timeout), **all fetchers must stop immediately** and no goroutine may remain blocked on `out <- result`.
## 🛠 The Challenge
Implement:
- `type DataFetcher struct { ... }`
- `func (f *DataFetcher) Fetch(ctx context.Context, urls []string) <-chan Result`
Where:
- `Result` contains `URL`, `Body []byte`, `Err error` (or similar).
### 1. Functional Requirements
- [ ] Start concurrent fetchers for all URLs (or bounded concurrency if you choose).
- [ ] Send results as they complete (order doesnt matter).
- [ ] Stop promptly on `ctx.Done()`.
- [ ] Close the output channel exactly once after all producers exit.
- [ ] Return partial results that already completed before cancellation.
### 2. The "Idiomatic" Constraints (Pass/Fail Criteria)
- [ ] **Every send uses select:** no bare `out <- x`.
- [ ] **Channel ownership:** only the producer side closes `out`.
- [ ] **No goroutine leaks:** all goroutines exit when ctx is canceled.
- [ ] **No double close:** prove it structurally (single closer goroutine). Avoid `sync.Once` unless you can justify it.
- [ ] **Buffer choice is intentional:** if you buffer, document why and how you chose the size.
### 3. Hints (Allowed Tools)
- You may use `errgroup` or a simple worker pattern, but the key is: **send must be cancel-aware**.
- If you do bounded concurrency, prefer `x/sync/semaphore` or a worker pool (but dont turn this kata into a rate-limiter kata).
## 🧪 Self-Correction (Test Yourself)
1. **Forgotten Sender**
- Start 50 fetchers, consume only 1 result, then cancel.
- **Pass:** goroutine count returns near baseline quickly (use `runtime.NumGoroutine()` as a sanity check).
2. **Cancellation Before First Receive**
- Cancel ctx immediately after calling `Fetch`.
- **Pass:** no goroutine blocks trying to send.
3. **Close Discipline**
- Cancel ctx from multiple places.
- **Pass:** no `panic: close of closed channel`.
## 📚 Resources
- https://go.dev/blog/pipelines
- https://www.ardanlabs.com/blog/2018/11/goroutine-leaks-the-forgotten-sender.html