WaitGroup

The conc.WaitGroup is a fundamental building block for structured concurrency in conc. It is a direct, safer replacement for sync.WaitGroup.

Its primary purpose is to run a group of goroutines and wait for them all to complete. Unlike the standard library's WaitGroup, conc.WaitGroup provides built-in panic handling.

Key Features

  • Automatic Panic Recovery: If a goroutine started with wg.Go() panics, the panic is caught.
  • Panic Propagation: The caught panic is re-thrown by wg.Wait(), ensuring that panics are not silently ignored.
  • Simple API: The API is minimal and easy to use, closely resembling sync.WaitGroup but without manual Add() and Done() calls.

Usage

Creating a WaitGroup

You can create a WaitGroup using its zero value or with the NewWaitGroup() constructor.

// Zero value is ready to use
var wg conc.WaitGroup

// Or using the constructor
wg2 := conc.NewWaitGroup()

Spawning Goroutines with Go()

The Go(f func()) method starts a new goroutine to execute the function f. It automatically handles the internal counter, similar to calling Add(1) and Done() in sync.WaitGroup.

var wg conc.WaitGroup
wg.Go(func() {
    fmt.Println("Hello from a goroutine!")
})

Waiting for Completion with Wait()

The Wait() method blocks until all goroutines started with Go() have finished. If any of the goroutines panicked, Wait() will propagate the first panic it captured.

package main

import (
    "fmt"
    "sync/atomic"

    "github.com/sourcegraph/conc"
)

func main() {
    var count atomic.Int64

    var wg conc.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Go(func() {
            count.Add(1)
        })
    }
    // This call will block until all 10 goroutines are done.
    wg.Wait()

    fmt.Println(count.Load()) // Output: 10
}

Panic Propagation Example

This example shows how a panic in a child goroutine is caught and re-panicked by Wait().

func TestWaitGroupPanic(t *testing.T) {
    var wg conc.WaitGroup
    wg.Go(func() {
        panic("super bad thing")
    })

    // The call to wg.Wait() will panic.
    require.Panics(t, wg.Wait)
}

Recovering Panics with WaitAndRecover()

If you need to handle the panic as a value instead of re-panicking, you can use WaitAndRecover(). It returns a *panics.Recovered object if a panic occurred, or nil otherwise.

func ExampleWaitGroup_WaitAndRecover() {
    var wg conc.WaitGroup

    wg.Go(func() {
        panic("super bad thing")
    })

    recoveredPanic := wg.WaitAndRecover()
    if recoveredPanic != nil {
        fmt.Println(recoveredPanic.Value) // Output: super bad thing
    }
}