# How Fabrica-Util's Closure Primitive Manages Thread-Safe Callbacks in Game Servers

> Fabrica-Util's Closure primitive ensures thread-safe callbacks execute once in game servers preventing data races. Learn how this synchronization wrapper protects concurrent goroutines.

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

---

**Fabrica-Util's Closure primitive is a synchronization wrapper that guarantees callbacks execute exactly once across concurrent goroutines while preventing data races.**

In high-concurrency game server architectures like those built with the go-pantheon ecosystem, managing callbacks safely is critical to avoiding data races and duplicate side effects. The `Closure` type defined in [`xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/closure.go) provides a zero-allocation, mutex-protected mechanism for executing arbitrary functions with guaranteed single-fire semantics.

## What Is the Closure Primitive?

The **Closure** primitive is a tiny synchronization wrapper that encapsulates a callback function—either `func() error` or `func(context.Context) error` for context-aware variants—and ensures it runs safely even when triggered simultaneously from multiple goroutines. Unlike a naïve function pointer, the `Closure` struct maintains an internal `sync.Mutex`, storage for the returned error, and boolean flags to track execution state.

This design isolates concurrency concerns to a single, well-tested component. Rather than re-implementing locking logic in every subsystem, game server components delegate callback safety to `Closure`, which resides at [`github.com/go-pantheon/fabrica-util/xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/github.com/go-pantheon/fabrica-util/xsync/closure.go).

## Why Game Servers Need Thread-Safe Callbacks

Game servers handling thousands of concurrent connections face specific challenges where unprotected callbacks cause catastrophic failures:

- **Event handling**: Player login notifications, match-making updates, and world-tick events must fire exactly once to prevent duplicate database writes or conflicting state updates.
- **Lifecycle hooks**: Connection teardown, resource cleanup, and graceful shutdown require idempotent execution to avoid double-closing sockets or leaking goroutines.
- **Asynchronous responses**: Background operations like AI calculations must respect cancellation signals when the server initiates shutdown.

Without synchronization, these callbacks risk **data races**, **panic-induced crashes**, and **duplicated work** when network retransmissions or multiple subsystems attempt to trigger the same event simultaneously.

## How Closure Guarantees Safe Execution

The implementation in [`xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/closure.go) solves these problems through several coordinated mechanisms:

**Mutex-protected execution**: The `Do()` method acquires an internal `sync.Mutex` before invoking the wrapped callback. This ensures that state-mutating operations—such as updating a player's session metadata—never run concurrently, eliminating race conditions.

**Idempotent single-fire semantics**: After the first successful invocation, `Closure` stores the returned error and a completion flag. Subsequent calls to `Do()` return the cached result without re-invoking the underlying function. This prevents duplicated side effects when events are published multiple times due to network retransmissions or redundant triggers.

**Context awareness**: The `ClosureCtx` variant accepts `func(context.Context) error` signatures and provides `DoCtx(ctx)`, allowing callbacks to abort early on timeout or cancellation. This enables graceful termination of long-running logic when the server receives a shutdown signal.

**Error propagation**: The wrapper stores the first error returned by the callback and returns it on every subsequent `Do()` call. Callers can inspect this result to determine whether to retry operations, log failures, or disconnect clients.

**Zero-allocation overhead**: The struct consists only of a function field, mutex, error slot, and boolean flags—minimal memory footprint critical for massively multiplayer servers managing millions of connections.

## Practical Implementation Examples

### Basic Usage Pattern

Create a `Closure` using the constructor and invoke it safely from multiple goroutines:

```go
package main

import (
    "fmt"
    "time"

    "github.com/go-pantheon/fabrica-util/xsync"
)

func updatePlayerScore() error {
    fmt.Println("updating score...")
    // Database or cache update logic here
    return nil
}

func main() {
    cl := xsync.NewClosure(updatePlayerScore)

    // Fire from five concurrent goroutines—only the first executes
    for i := 0; i < 5; i++ {
        go func(id int) {
            if err := cl.Do(); err != nil {
                fmt.Printf("goroutine %d: %v\n", id, err)
            }
        }(i)
    }

    time.Sleep(100 * time.Millisecond)
}

```

### Context-Aware Execution

For operations requiring cancellation support, use `NewClosureCtx` with timeout constraints:

```go
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()

ctxClosure := xsync.NewClosureCtx(func(ctx context.Context) error {
    select {
    case <-time.After(30 * time.Millisecond):
        fmt.Println("work completed")
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
})

if err := ctxClosure.DoCtx(ctx); err != nil {
    fmt.Println("operation aborted:", err)
}

```

### Real-World Game Server Pattern

In a gateway component (similar to the Janus architecture), ensure connections close exactly once:

```go
type ConnHandler struct {
    closeOnce xsync.Closure
}

func NewConnHandler(conn net.Conn) *ConnHandler {
    h := &ConnHandler{}
    h.closeOnce = xsync.NewClosure(func() error {
        return conn.Close()
    })
    return h
}

// Safe to call from read loop, write loop, or shutdown signal
func (h *ConnHandler) Close() error {
    return h.closeOnce.Do()
}

```

## Integration with Higher-Level Utilities

The `Closure` primitive serves as the foundational building block for fabrica-util's advanced synchronization utilities:

- **`Delayer`** ([`xsync/delayer.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/delayer.go)): Time-based task scheduling relies on `Closure` to ensure delayed callbacks execute safely when their timer fires.
- **`Future`** ([`xsync/future.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/future.go)): Asynchronous result handling uses `Closure` to wrap completion callbacks, preventing double-completion when multiple goroutines resolve the same future.
- **`Routines`** ([`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go)): Goroutine lifecycle management utilities employ the same mutex patterns pioneered in `Closure` for coordinated shutdown.

By centralizing callback safety in [`xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/closure.go), these higher-level components avoid duplicating concurrency logic while maintaining consistent behavior across the Roma, Janus, Lares, and Senate server architectures.

## Summary

- **Fabrica-Util's Closure** provides mutex-protected, single-fire callback execution in [`xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/closure.go).
- **`Do()`** guarantees the wrapped function runs exactly once regardless of how many goroutines invoke it simultaneously.
- **Context-aware variants** (`ClosureCtx`) support cancellation and timeout propagation for long-running operations.
- **Zero-allocation design** minimizes per-connection overhead for high-scale game servers.
- **Error caching** allows callers to inspect initial failures while preventing side-effect duplication.
- Higher-level utilities like `Delayer` and `Future` build upon `Closure` for complex scheduling and async patterns.

## Frequently Asked Questions

### How does Closure prevent callbacks from running multiple times?

`Closure` maintains an internal boolean flag that flips to true after the first successful `Do()` invocation. The method acquires a mutex, checks this flag, and returns the cached result if execution already occurred. This ensures the underlying `func() error` or `func(context.Context) error` executes exactly once even when triggered concurrently from thousands of goroutines.

### What is the difference between NewClosure and NewClosureCtx?

`NewClosure` wraps a standard `func() error` and provides the `Do()` method, suitable for fire-and-forget operations. `NewClosureCtx` wraps `func(context.Context) error` and provides `DoCtx(ctx)`, enabling the callback to respect cancellation signals, timeouts, and deadlines—critical for graceful server shutdowns and preventing resource leaks in long-running computations.

### Can Closure handle panics in the wrapped function?

The current implementation in [`xsync/closure.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/closure.go) does not automatically recover panics. The wrapped callback should implement its own panic recovery if needed. However, the mutex is released via deferred unlock, so even if the wrapped function panics, the lock will not remain held, preventing deadlocks in the calling goroutines.

### How does Closure fit into the broader fabrica-util synchronization toolkit?

`Closure` is the primitive upon which `Delayer`, `Future`, and `Routines` are built. For example, `Delayer` uses `Closure` to ensure delayed tasks execute safely when their scheduled time arrives, while `Future` uses it to guarantee completion callbacks fire exactly once. This layering allows game server developers to use high-level abstractions while trusting that the underlying synchronization is handled consistently and efficiently.