# fabrica-util Routines vs Standard Go Goroutines: 6 Key Differences for Concurrent Game Logic

> Compare fabrica-util Routines and Go goroutines for game logic. Discover key differences in panic recovery, error logging, and filtering for robust concurrency.

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

---

**fabrica-util's Routines provide automatic panic recovery, structured error logging, and configurable error filtering that raw Go goroutines lack, specifically designed for robust concurrent game server logic.**

The `go-pantheon/fabrica-util` repository offers a specialized concurrency abstraction in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go) that wraps standard Go goroutines with game-server-specific safety and observability features. While standard goroutines require manual error handling and panic recovery, fabrica-util's **Routines** automatically manage fault isolation, centralized logging, and timeout coordination—critical requirements for high-throughput game backends running thousands of concurrent operations.

## Automatic Panic Recovery and Error Wrapping

Standard Go goroutines crash the entire program when a panic occurs unless developers manually implement `defer recover()` blocks in every function. In contrast, fabrica-util's **`Go`** and **`Run`** functions automatically defer a recovery mechanism that intercepts panics before they propagate.

According to the source code in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go) (lines [63-73](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L63-L73)), the wrapper captures the panic value, converts it into a proper `error` via the **`CatchErr`** function, and enriches it with a full stack trace using `errors.StackTrace`. This transformation occurs transparently, ensuring that a panic in one game session—such as an AI logic error or physics calculation overflow—cannot terminate the entire server process.

The **`CatchErr`** implementation (lines [32-50](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L32-L50)) constructs the error using `errors.Wrap` to maintain the original context while adding the runtime stack information. A deprecated **`CatchErrWithSize`** variant remains available for backward compatibility (lines [52-59](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L52-L59)).

## Structured Logging with Error Filtering

Raw goroutines require manual logging instrumentation inside each function, leading to inconsistent observability across game modules. The fabrica-util **`Go`** function centralizes structured logging through `slog.Error`, automatically emitting log entries for both recovered panics and function-returned errors (lines [75-82](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L75-L82)).

Additionally, the system provides **error filtering** capabilities absent in standard goroutines. The `Go` function accepts optional variadic filters (`filters ...func(err error) bool`) that evaluate returned errors before logging. If any filter returns `true`, the error is silently discarded (lines [53-61](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L53-L61)). This feature allows game developers to suppress expected errors—such as "player disconnected" or "session timeout"—while ensuring genuine bugs surface in logs.

## Built-in Timeout Utilities

Standard Go requires manual orchestration of `context.WithTimeout`, channel coordination, and goroutine synchronization to implement operation timeouts. fabrica-util abstracts this pattern into the **`Timeout`** function (lines [20-46](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L20-L46)), which accepts a context, function name, closure, and duration.

The wrapper automatically creates the timeout context, executes the function via the internal `Go` mechanism, and selects between three outcomes: successful completion, error return, or context deadline exceeded. This pattern is essential for game operations like match-making RPCs or database queries that must not block indefinitely.

## Routine Identification for Debugging

Go's runtime does not expose a public API for retrieving the current goroutine's identifier. fabrica-util addresses this diagnostic gap with **`RoutineID`**, which parses the runtime stack trace to extract the numeric goroutine ID (lines [99-130](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L99-L130)).

As noted in the source comments (lines [99-101](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go#L99-L101)), this identifier is strictly intended for debugging and profiling, not for production business logic. Developers can correlate log entries with specific execution contexts during post-mortem analysis of concurrent game state issues.

## Practical Implementation Examples

### Safe Goroutine Execution with Error Filtering

```go
package main

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

var ErrPlayerDisconnected = errors.New("player disconnected")

func main() {
	// Define a filter that silences expected disconnection errors.
	ignoreDisconnect := func(err error) bool {
		return errors.Is(err, ErrPlayerDisconnected)
	}

	// Launch a monitored goroutine that automatically recovers panics
	// and filters out disconnection errors from logs.
	xsync.Go("handle player session", func() error {
		return processPlayerConnection()
	}, ignoreDisconnect)
}

func processPlayerConnection() error {
	// Game logic here...
	return nil
}

```

The `Go` wrapper ensures any panic within `processPlayerConnection` is recovered, wrapped with a stack trace, and logged—unless the error matches the `ignoreDisconnect` filter.

### Timeout Handling for Network Operations

```go
ctx := context.Background()

err := xsync.Timeout(ctx, "fetch match state",
	func() error {
		// Simulate a blocking RPC that may hang.
		return fetchMatchDataFromService()
	},
	2*time.Second,
)

if err != nil {
	// Handles both RPC errors and timeout (context.DeadlineExceeded) uniformly.
	log.Error("match fetch failed", "error", err)
}

```

This pattern eliminates boilerplate associated with `context.WithTimeout` and channel management for deadline enforcement.

### Debugging with Routine IDs

```go
go func() {
	id := xsync.RoutineID()
	log.Info("started physics simulation tick", "goroutine_id", id)
	
	// ... simulation logic ...
	
	log.Info("completed tick", "goroutine_id", id)
}()

```

The `RoutineID` function parses the runtime stack to provide diagnostic correlation between log entries and specific execution contexts.

## Summary

- **Automatic panic recovery**: Converts panics into manageable errors with stack traces, preventing server crashes (lines 63-73 in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go)).
- **Centralized structured logging**: Emits `slog.Error` entries for all unfiltered errors and recovered panics without manual instrumentation (lines 75-82).
- **Configurable error filtering**: Accepts predicate functions to suppress expected errors like disconnections (lines 53-61).
- **Timeout abstraction**: The `Timeout` function wraps context creation and deadline handling into a single call (lines 20-46).
- **Diagnostic routine IDs**: `RoutineID` extracts the numeric goroutine identifier from the runtime stack for debugging purposes (lines 99-130).
- **Custom error wrapping**: `CatchErr` transforms panic values into proper `error` types with full stack traces (lines 32-50).

## Frequently Asked Questions

### When should I use fabrica-util Routines instead of raw goroutines?

Use fabrica-util Routines when building concurrent game server logic that requires fault isolation, such as player session handlers, AI loops, or network processors. Raw goroutines are sufficient for short-lived, fire-and-forget operations where panic recovery and error tracking are not critical. The automatic recovery and logging features become essential when running thousands of persistent concurrent loops where a single panic could otherwise terminate the entire process.

### How does the error filtering mechanism work in fabrica-util Routines?

The `Go` and `Run` functions accept variadic filter functions (`...func(err error) bool`). When the wrapped function returns an error or a panic is recovered, each filter is evaluated in sequence. If any filter returns `true`, the error is silently discarded and not logged. This allows developers to define domain-specific predicates—such as checking for `ErrPlayerDisconnected` or `ErrTimeoutExpected`—to prevent routine, expected failures from flooding structured logs while ensuring genuine bugs are captured.

### Can RoutineID be used for production game logic like session mapping?

No. According to the source code comments in [`xsync/routines.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xsync/routines.go) (lines 99-101), `RoutineID` is strictly intended for **diagnostic and debugging purposes only**. The function works by parsing the runtime stack trace, which is an implementation detail that could change between Go versions. It should not be used to index player sessions, associate game state with specific goroutines, or drive business logic decisions in production environments.

### What happens when a panic occurs inside a fabrica-util Routine?

When a panic occurs, the deferred recovery mechanism in `Go` or `Run` intercepts the panic value before it propagates. The value is passed to `CatchErr`, which constructs an `error` containing the original panic message and a full `errors.StackTrace`. This error is then logged via `slog.Error` with the function name and stack information, unless filtered. The goroutine terminates cleanly after logging, leaving all other server goroutines unaffected—unlike raw goroutines, where an unrecovered panic crashes the entire program.