# Fabrica-Util XSync Synchronization Primitives: Stopper, Future, and Delayer Explained

> Explore fabrica-util xsync primitives like Stopper, Future, and Delayer to manage goroutine lifecycles effectively. Simplify complex concurrency in Go.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: deep-dive
- Published: 2026-03-02

---

**The `xsync` package in `go-pantheon/fabrica-util` provides four core synchronization primitives—`Stopper` for graceful shutdown coordination, generic `Future[T]` for promise-style async results, `Delayer` for resettable timers, and panic-safe goroutine helpers—to manage complex goroutine lifecycles without heavyweight frameworks.**

The `xsync` package (located at `xsync/` in the `go-pantheon/fabrica-util` repository) offers lightweight, composable **synchronization primitives** designed for production Go applications. These tools handle common concurrency patterns—coordinated shutdown, deferred value retrieval, and timed scheduling—while eliminating repetitive boilerplate for panic recovery and structured logging. Understanding when to use each primitive helps you build resilient services that gracefully manage goroutine orchestration.

## Stopper: Coordinated Graceful Shutdown

The **Stopper** type (defined in [`xsync/stopper.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/stopper.go)) implements a finite state machine (transiting through `stateIdle` → `stateTriggered` → `stopping` → `stateStopped`) that orchestrates clean shutdown across multiple goroutines. It provides a centralized mechanism to broadcast stop signals while enforcing timeout boundaries.

Key capabilities include:

- **`StopTriggered()`** – Returns a receive-only channel that closes once shutdown begins, allowing workers to select on stop signals.
- **`TurnOff(fn)`** – Executes a user-supplied shutdown function with a configurable deadline, returning `ErrTurnOffTimeout` if execution exceeds the limit.
- **Lifecycle-aware goroutine launchers** – **`Go`**, **`GoWaitStop`**, and **`GoAndStop`** start workers that automatically respect the stopper's lifecycle and capture panics.

When to use **Stopper**: Deploy this primitive for any long-running service (HTTP servers, background worker pools, or message consumers) that must react to `SIGTERM`/`SIGINT` signals. It ensures in-flight requests complete or abort cleanly within defined boundaries rather than terminating abruptly.

```go
// Create a stopper that aborts shutdown after 5 seconds
stopper := xsync.NewStopper(5 * time.Second)

// Start a worker that respects the stop signal
stopper.Go("worker.loop", func() error {
    for {
        select {
        case <-stopper.StopTriggered():
            // Perform cleanup before exiting
            return nil
        default:
            // Regular work execution
            time.Sleep(100 * time.Millisecond)
        }
    }
})

// Trigger shutdown from signal handler or admin endpoint
if err := stopper.Stop(context.Background()); err != nil {
    slog.Error("shutdown failed", "error", err)
}
<-stopper.WaitStopped() // Block until all workers finish

```

## Future[T]: Type-Safe Asynchronous Results

The **Future[T]** type (implemented in [`xsync/future.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/future.go)) provides a generic promise-style container for values produced by concurrent operations. Unlike channels that require continuous polling, Future offers one-shot completion semantics with multiple retrieval options.

Core API surface:

- **`Complete(value, err)`** – Idempotently sets the result exactly once; subsequent calls have no effect.
- **`Get()`** – Blocks indefinitely until completion.
- **`GetWithContext(ctx)`** and **`GetWithTimeout(d)`** – Provide cancellation and deadline propagation.
- **`Cancel()`** – Marks the future as cancelled, causing retrievers to receive `ErrFutureCancelled`.

When to use **Future**: Employ this primitive when a caller needs a value computed by another goroutine (such as database lookups, remote RPC calls, or background calculations) without blocking indefinitely. It supports multiple concurrent waiters, making it ideal for fan-out/fan-in patterns where several consumers await the same result.

```go
// Create a typed future for integer results
f := xsync.NewFuture[int]()

// Produce the value asynchronously
xsync.Go("calc", func() error {
    time.Sleep(200 * time.Millisecond)
    f.Complete(42, nil)
    return nil
})

// Consumer waits with timeout context
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

value, err := f.GetWithContext(ctx)
if err != nil {
    slog.Error("future failed", "error", err)
} else {
    slog.Info("result received", "value", value)
}

```

## Delayer: Resettable Expiry Timers

The **Delayer** type (located in [`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go)) implements the **`Delayable`** and **`WorkerDelayable`** interfaces to provide a resettable timer that can be extended or cancelled after creation. Unlike standard `time.Timer` instances that fire once and cannot be reused reliably, Delayer supports dynamic deadline adjustments.

Interface methods include:

- **`SetExpiryTime(t)`** – Schedules or reschedules the timer to fire at the specified `time.Time`.
- **`Reset()`** – Clears the current timer and marks the delayer as stopped.
- **`Close()`** – Permanently stops the timer and releases resources.
- **`Wait()`** – Returns a channel that receives a signal upon expiry.
- **`IsExpired()`** and **`TimeRemaining()`** – Query the current timer state.

When to use **Delayer**: Choose this primitive for scenarios requiring mutable deadlines, such as exponential backoff retry logic, debouncing rapid user input events, or scheduling one-off tasks that may need rescheduling before execution.

```go
delayer := xsync.NewDelayer()

// Debounce: reset timer to 300ms on each event
handleEvent := func() {
    delayer.SetExpiryTime(time.Now().Add(300 * time.Millisecond))
}

// Wait for debounce to fire
xsync.Go("debounce.wait", func() error {
    <-delayer.Wait()
    slog.Info("debounce fired - processing aggregated events")
    return nil
})

// Simulate rapid incoming events
for i := 0; i < 5; i++ {
    handleEvent()
    time.Sleep(100 * time.Millisecond)
}

```

## Utility Helpers: Panic-Safe Goroutine Management

The **`xsync`** package includes runtime utilities (defined in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go)) that wrap goroutine execution with standardized panic recovery, structured logging, and timeout enforcement.

Available helpers:

- **`Go(msg, fn, filters...)`** – Launches `fn` in a new goroutine, recovers from panics, logs errors with the provided message context, and optionally filters expected errors from logs.
- **`Run(fn)`** – Executes `fn` synchronously with panic recovery, returning any recovered error.
- **`Timeout(ctx, msg, fn, d, filters...)`** – Runs `fn` via `Go` but aborts if execution exceeds duration `d` or the context deadline.
- **`RoutineID()`** – Parses the runtime stack to extract the current goroutine ID for debug correlation.

When to use these helpers: Integrate these wrappers whenever you launch background work to eliminate repetitive `recover`/`defer` boilerplate. They integrate seamlessly with **Stopper** (via `stopper.Go`) and provide consistent observability across your application's goroutine fleet.

```go
// Execute with hard timeout
err := xsync.Timeout(
    context.Background(),
    "critical.task",
    func() error {
        // Simulate long-running operation
        time.Sleep(5 * time.Second)
        return nil
    },
    2*time.Second,
)

if err != nil {
    // Captures timeout or panic information
    slog.Error("task failed", "error", err)
}

```

## Summary

- **`Stopper`** ([`xsync/stopper.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/stopper.go)) orchestrates graceful shutdown via state-machine-driven lifecycle management with timeout enforcement.
- **`Future[T]`** ([`xsync/future.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/future.go)) delivers type-safe, promise-style asynchronous results supporting context cancellation and multiple waiters.
- **`Delayer`** ([`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go)) provides resettable, queryable timers ideal for backoff strategies and debouncing.
- **Routines** ([`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go)) offer panic-protected goroutine launching with structured logging and deadline enforcement through `Go`, `Run`, and `Timeout`.

## Frequently Asked Questions

### How does Stopper enforce shutdown timeouts?

The `Stopper` enforces timeouts through the `TurnOff` method and internal deadline tracking established via `NewStopper(duration)`. If a shutdown function exceeds the specified duration, the method returns `ErrTurnOffTimeout`, ensuring that stuck workers cannot block application termination indefinitely.

### Can multiple goroutines wait on a single Future value?

Yes, **Future[T]** supports multiple concurrent consumers. Any number of goroutines can call `Get`, `GetWithContext`, or `GetWithTimeout` on the same Future instance. Once `Complete` is called, all waiting goroutines receive the result value or error simultaneously.

### What distinguishes Delayer from a standard time.Timer?

While `time.Timer` fires once at a fixed deadline and cannot be safely reset after expiry without potential races, **Delayer** provides the `SetExpiryTime` method to dynamically adjust deadlines and the `Reset` method to cancel pending timers cleanly. This reset capability makes Delayer appropriate for debouncing where the trigger time shifts based on incoming events.

### How does the Go helper recover from panics?

The `Go` function (and by extension `Timeout`) uses `defer` with `recover` to catch panics within the launched goroutine. It logs the panic details using the provided message string and optional error filters, then converts the panic into a returned error without crashing the application. This pattern ensures that background workers fail safely and observably.