Skip to main content

design-patterns-wait-group

Use when running concurrent operations with error collection, structuring parallel or serial work with cleanup, or encountering MakeWaitGroupParallel, MakeWaitGroupSerial, DoAfter, or GroupBuilder in code.

Datos de origen

Repositorio
amarbel-llc/dodder
Última actividad en el origen
26 de febrero de 2026 a las 13:16
Idioma detectado de SKILL.md
inglés
Estrellas
2
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
design_patterns-wait_group
description
Use when running concurrent operations with error collection, structuring parallel or serial work with cleanup, or encountering MakeWaitGroupParallel, MakeWaitGroupSerial, DoAfter, or GroupBuilder in code.
triggers
["WaitGroup","MakeWaitGroupParallel","MakeWaitGroupSerial","DoAfter","GroupBuilder","error aggregation","parallel operations","concurrent error handling"]
# Wait Group with Error Aggregation ## Overview Dodder's wait groups extend Go's `sync.WaitGroup` with automatic error collection, ordered cleanup via `DoAfter`, and stack frame capture in debug builds. Two variants exist: parallel (concurrent goroutines) and serial (sequential execution). Both implement the same `WaitGroup` interface and aggregate all errors into a single return value. ## Interface ```go // alfa/errors (interfaces) type WaitGroup interface { Do(FuncErr) bool // Queue work. Returns false if already done. DoAfter(FuncErr) // Queue cleanup (runs in reverse order after Do completes). GetError() error // Wait for all work + cleanup, return aggregated errors. } ``` ## Creating Wait Groups ```go wg := errors.MakeWaitGroupParallel() // concurrent — each Do() runs in its own goroutine wg := errors.MakeWaitGroupSerial() // sequential — Do() functions run in order during GetError() ``` ## Usage Pattern ```go wg := errors.MakeWaitGroupParallel() wg.Do(func() error { return flushIndex() }) wg.Do(func() error { return flushAbbreviations() }) wg.DoAfter(func() error { return releaseLock() }) if err := wg.GetError(); err != nil { // err contains all errors from Do() and DoAfter() } ``` ## How It Works ### Parallel - Each `Do()` call launches a goroutine immediately - Errors are collected thread-safely via `GroupBuilder` (mutex-protected) - `GetError()` waits for all goroutines, then runs `DoAfter` functions in reverse order (LIFO), then returns aggregated errors ### Serial - `Do()` stores functions in a slice - `GetError()` executes them sequentially, collecting errors - `DoAfter` functions run in reverse order after all `Do` functions complete ### Error Aggregation `GroupBuilder` collects errors thread-safely: ```go // alfa/errors/group_builder.go type GroupBuilder struct { lock sync.Mutex group Group } ``` - `Add()` automatically flattens nested `Group` errors when collecting - `GetError()` returns `nil` if no errors, or a `Group` containing all errors ## DoAfter Ordering `DoAfter` functions execute in **reverse registration order** (LIFO), similar to `defer`: ```go wg.DoAfter(func() error { return cleanup1() }) // runs second wg.DoAfter(func() error { return cleanup2() }) // runs first ``` ## Debug Stack Capture In debug builds, `Do()` captures the caller's stack frame via `stack_frame.MakeFrame(1)`. This aids in diagnosing which call site produced an error when multiple parallel operations fail. ## Real-World Examples ### Parallel flush ```go // tango/store/flush.go wg := errors.MakeWaitGroupParallel() wg.Do(func() error { return store.streamIndex.Flush(printerHeader) }) wg.Do(store.GetAbbrStore().Flush) wg.Do(store.zettelIdIndex.Flush) if err = wg.GetError(); err != nil { return errors.Wrap(err) } ``` ### Parallel query with cleanup ```go // papa/store_fs/query.go wg := errors.MakeWaitGroupParallel() wg.Do(func() (err error) { for item := range store.probablyCheckedOut.All() { if err = process(item); err != nil { return } } return }) wg.DoAfter(index.Flush) err = wg.GetError() ``` ## Common Mistakes | Mistake | Correct Approach | |---------|-----------------| | Using `sync.WaitGroup` + manual error channels | Use `MakeWaitGroupParallel()` for built-in error aggregation | | Ignoring `Do()` return value | Returns `false` if wait group is already done — check when conditional | | Putting cleanup in `Do()` instead of `DoAfter()` | `DoAfter` guarantees execution after all `Do` work completes | | Calling `GetError()` multiple times | Call once — it waits and drains all work |
Ver en GitHub