$npx -y skills add fusengine/agents --skill go-concurrencyUse when: writing or reviewing Go concurrency — goroutines, channels, golang.org/x/sync/errgroup, context propagation and cancellation, sync.WaitGroup vs channels, the -race detector, or diagnosing goroutine leaks (incl. the 1.26 goroutineleak profile). Do NOT use for: sequential
| 1 | # Go Concurrency |
| 2 | |
| 3 | Goroutines, channels, `context`, and `errgroup` for Go 1.26 — plus the number-one |
| 4 | documented pitfall: **leaking goroutines on an unbuffered channel + early return.** |
| 5 | |
| 6 | ## Agent Workflow (MANDATORY) |
| 7 | |
| 8 | Before ANY implementation, use `TeamCreate` to spawn 3 agents: |
| 9 | |
| 10 | 1. **fuse-ai-pilot:explore-codebase** - Map existing goroutine/channel/context usage |
| 11 | 2. **fuse-ai-pilot:research-expert** - Verify errgroup/context docs via Context7/Exa |
| 12 | 3. **mcp__context7__query-docs** - Confirm `golang.org/x/sync/errgroup` signatures |
| 13 | |
| 14 | After implementation, run **fuse-ai-pilot:sniper** for validation, and run tests |
| 15 | with `go test -race ./...`. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Overview |
| 20 | |
| 21 | | Feature | Description | |
| 22 | |---------|-------------| |
| 23 | | **Goroutines & channels** | Lightweight concurrency + typed communication | |
| 24 | | **errgroup** | Parallelism + error aggregation + context cancellation | |
| 25 | | **context** | First param, propagated strictly, carries cancellation/deadline | |
| 26 | | **WaitGroup vs channels** | Counting-only vs result/error passing | |
| 27 | | **Race detector** | `-race` in tests/CI to catch data races | |
| 28 | | **Leak profile (1.26)** | `GOEXPERIMENT=goroutineleakprofile` / `/debug/pprof/goroutineleak` | |
| 29 | |
| 30 | --- |
| 31 | |
| 32 | ## Critical Rules |
| 33 | |
| 34 | 1. **`context.Context` is the first parameter** - named `ctx`, never stored in a struct |
| 35 | 2. **Every started goroutine must be able to exit** - or it leaks (see rule 4) |
| 36 | 3. **Prefer `errgroup` for fan-out with errors** - it handles wait + first error + cancel |
| 37 | 4. **Unbuffered channel + early return = leak** - senders block forever; buffer or drain |
| 38 | 5. **Test with `-race`** - a passing test without `-race` proves nothing about races |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## Architecture |
| 43 | |
| 44 | ``` |
| 45 | internal/ |
| 46 | ├── fetch/ |
| 47 | │ ├── fetch.go # errgroup.WithContext fan-out, bounded by SetLimit |
| 48 | │ └── worker.go # worker pool: fixed goroutines drain a jobs channel |
| 49 | └── pipeline/ |
| 50 | └── stage.go # ctx-cancellable stages, buffered hand-off channels |
| 51 | ``` |
| 52 | |
| 53 | → See [errgroup-patterns.md](references/templates/errgroup-patterns.md) for full example |
| 54 | |
| 55 | --- |
| 56 | |
| 57 | ## Reference Guide |
| 58 | |
| 59 | ### Concepts |
| 60 | |
| 61 | | Topic | Reference | When to Consult | |
| 62 | |-------|-----------|-----------------| |
| 63 | | **Goroutines & channels** | [goroutines-channels.md](references/goroutines-channels.md) | Buffered vs not, select, WaitGroup vs channels | |
| 64 | | **errgroup** | [errgroup.md](references/errgroup.md) | Fan-out, error aggregation, SetLimit, TryGo | |
| 65 | | **context** | [context-propagation.md](references/context-propagation.md) | Cancellation, deadlines, propagation rules | |
| 66 | | **Goroutine leaks** | [goroutine-leaks.md](references/goroutine-leaks.md) | The #1 pitfall + the 1.26 leak profile | |
| 67 | |
| 68 | ### Templates |
| 69 | |
| 70 | | Template | When to Use | |
| 71 | |----------|-------------| |
| 72 | | [errgroup-patterns.md](references/templates/errgroup-patterns.md) | Bounded parallel work with error handling | |
| 73 | | [worker-pool.md](references/templates/worker-pool.md) | Fixed workers draining a job queue | |
| 74 | |
| 75 | --- |
| 76 | |
| 77 | ## Quick Reference |
| 78 | |
| 79 | ### Parallel work with errgroup |
| 80 | |
| 81 | ```go |
| 82 | g, ctx := errgroup.WithContext(ctx) |
| 83 | g.SetLimit(8) // bound concurrency |
| 84 | for _, u := range urls { |
| 85 | g.Go(func() error { return fetch(ctx, u) }) |
| 86 | } |
| 87 | if err := g.Wait(); err != nil { // first non-nil error; cancels ctx |
| 88 | return err |
| 89 | } |
| 90 | ``` |
| 91 | |
| 92 | → See [errgroup.md](references/errgroup.md) |
| 93 | |
| 94 | ### Avoid the leak (buffer so senders never block) |
| 95 | |
| 96 | ```go |
| 97 | ch := make(chan result, len(items)) // buffered → early return can't strand senders |
| 98 | ``` |
| 99 | |
| 100 | → See [goroutine-leaks.md](references/goroutine-leaks.md) |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## Best Practices |
| 105 | |
| 106 | ### DO |
| 107 | - Pass `ctx` first and thread it through every blocking call |
| 108 | - Reach for `errgroup` before hand-rolling `WaitGroup` + error channels |
| 109 | - Buffer result channels to the number of senders, or fully drain them |
| 110 | - Run `go test -race`; try `GOEXPERIMENT=goroutineleakprofile` in CI (1.26) |
| 111 | |
| 112 | ### DON'T |
| 113 | - Return early from a fan-out while goroutines still block on an unbuffered channel |
| 114 | - Store a `context.Context` in a struct field |
| 115 | - Use a bare `sync.WaitGroup` when goroutines return errors (use `errgroup`) |
| 116 | - Assume tests are race-free without the `-race` flag |