Documentation
¶
Overview ¶
Package gofuncy provides context-aware, observable goroutine management with built-in resilience patterns (retry, circuit breaker, fallback).
It replaces raw go func() calls with structured APIs that propagate context, recover from panics, and emit OpenTelemetry metrics and traces.
Index ¶
- Constants
- Variables
- func All[T any](ctx context.Context, items []T, fn func(ctx context.Context, item T) error, ...) error
- func Do(ctx context.Context, fn Func, opts ...GoOption) error
- func Go(ctx context.Context, fn Func, opts ...GoOption)
- func Map[T, R any](ctx context.Context, items []T, ...) ([]R, error)
- func NameFromContext(ctx context.Context) string
- func ParentFromContext(ctx context.Context) string
- func Start(ctx context.Context, fn Func, opts ...GoOption)
- func StartWithReady(ctx context.Context, fn func(ctx context.Context, ready ReadyFunc) error, ...)
- func StartWithStop(ctx context.Context, fn func(ctx context.Context, stop StopFunc) error, ...)
- func Wait(ctx context.Context, fn Func, opts ...GoOption) func() error
- func WaitWithReady(ctx context.Context, fn func(ctx context.Context, ready ReadyFunc) error, ...) func() error
- func WaitWithStop(ctx context.Context, fn func(ctx context.Context, stop StopFunc) error, ...) func() error
- func WithCallerSkip(skip int) goOnlyOpt
- func WithChildTrace() baseOpt
- func WithCircuitBreaker(cb *CircuitBreaker) baseOpt
- func WithDetachedTrace() baseOpt
- func WithDurationHistogram() baseOpt
- func WithErrorHandler(h ErrorHandler) goOnlyOpt
- func WithFailFast() groupOnlyOpt
- func WithFallback(fn func(ctx context.Context, err error) error, opts ...FallbackOption) baseOpt
- func WithLimit(n int) groupOnlyOpt
- func WithLimiter(l *semaphore.Weighted) baseOpt
- func WithLogger(l *slog.Logger) baseOpt
- func WithMeterProvider(mp metric.MeterProvider) baseOpt
- func WithMiddleware(m ...Middleware) baseOpt
- func WithName(name string) baseOpt
- func WithRetry(maxAttempts int, opts ...RetryOption) baseOpt
- func WithStallHandler(h StallHandler) baseOpt
- func WithStallThreshold(d time.Duration) baseOpt
- func WithTimeout(timeout time.Duration) baseOpt
- func WithTracerProvider(tp trace.TracerProvider) baseOpt
- func WithoutActiveUpDownCounter() baseOpt
- func WithoutErrorCounter() baseOpt
- func WithoutStartedCounter() baseOpt
- func WithoutTracing() baseOpt
- type Backoff
- type CircuitBreaker
- type CircuitBreakerOption
- type CircuitState
- type Context
- type ErrorHandler
- type FallbackOption
- type Func
- type GoOption
- type Group
- type GroupOption
- type Middleware
- type PanicError
- type ReadyFunc
- type RetryOption
- type StallHandler
- type StopFunc
Examples ¶
Constants ¶
const ( // NameRoot is the default root name used for setting or retrieving the root context in a routine hierarchy. NameRoot string = "root" // NameNoName is the default name used when no routine name is explicitly set. NameNoName string = "noname" )
const ScopeName = "github.com/foomo/gofuncy"
ScopeName is the OpenTelemetry instrumentation scope name used by all gofuncy metrics and traces.
Variables ¶
var ErrCircuitOpen = errors.New("circuit breaker is open")
ErrCircuitOpen is returned when the circuit breaker is open and not accepting requests.
Functions ¶
func All ¶
func All[T any](ctx context.Context, items []T, fn func(ctx context.Context, item T) error, opts ...GroupOption) error
All executes fn for each item concurrently and returns the joined errors. All GroupOption options apply (WithLimit, WithFailFast, telemetry, etc.). Use WithName to set a custom metric/tracing label; defaults to "gofuncy.all".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
urls := []string{"https://a.test", "https://b.test", "https://c.test"}
err := gofuncy.All(context.Background(), urls,
func(ctx context.Context, url string) error {
fmt.Println("fetching", url)
return nil
},
)
fmt.Println("error:", err)
}
Output: fetching https://a.test fetching https://b.test fetching https://c.test error: <nil>
func Do ¶
Do executes fn synchronously with the full middleware chain (resilience, telemetry, tracing) and returns the error directly. Unlike Go, it does not spawn a goroutine. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.do".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
err := gofuncy.Do(context.Background(), func(ctx context.Context) error {
fmt.Println("hello")
return nil
})
fmt.Println("error:", err)
}
Output: hello error: <nil>
Example (Fallback) ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
err := gofuncy.Do(context.Background(),
func(ctx context.Context) error {
return fmt.Errorf("primary failed")
},
gofuncy.WithFallback(func(ctx context.Context, err error) error {
fmt.Println("fallback called:", err)
return nil // suppress the error
}),
)
fmt.Println("error:", err)
}
Output: fallback called: primary failed error: <nil>
Example (Retry) ¶
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/foomo/gofuncy"
)
func main() {
var attempts atomic.Int32
err := gofuncy.Do(context.Background(),
func(ctx context.Context) error {
n := attempts.Add(1)
if n < 3 {
return fmt.Errorf("attempt %d failed", n)
}
fmt.Println("succeeded on attempt", n)
return nil
},
gofuncy.WithRetry(5),
gofuncy.WithTimeout(time.Second),
)
fmt.Println("error:", err)
}
Output: succeeded on attempt 3 error: <nil>
func Go ¶
Go spawns a fire-and-forget goroutine with panic recovery. Errors are logged via slog by default; use WithErrorHandler to override. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.go".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
done := make(chan struct{})
gofuncy.Go(context.Background(), func(ctx context.Context) error {
defer close(done)
fmt.Println("running")
return nil
})
<-done
}
Output: running
func Map ¶
func Map[T, R any](ctx context.Context, items []T, fn func(ctx context.Context, item T) (R, error), opts ...GroupOption) ([]R, error)
Map transforms items concurrently, preserving input order. Returns results and the joined errors. All GroupOption options apply (WithLimit, WithFailFast, telemetry, etc.). Use WithName to set a custom metric/tracing label; defaults to "gofuncy.map".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
items := []int{1, 2, 3, 4, 5}
results, err := gofuncy.Map(context.Background(), items,
func(ctx context.Context, n int) (int, error) {
return n * 2, nil
},
)
fmt.Println("results:", results)
fmt.Println("error:", err)
}
Output: results: [2 4 6 8 10] error: <nil>
func NameFromContext ¶
NameFromContext extracts the routine name from the given context, falling back to a default "noname" if not found.
func ParentFromContext ¶
ParentFromContext extracts the parent routine name from the given context.
func Start ¶ added in v0.2.0
Start is like Go but blocks until the goroutine has actually started executing. This is useful in tests where you need the routine to be running before proceeding. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.start".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
done := make(chan struct{})
gofuncy.Start(context.Background(), func(ctx context.Context) error {
defer close(done)
fmt.Println("running")
return nil
})
<-done
}
Output: running
func StartWithReady ¶ added in v0.2.0
func StartWithReady(ctx context.Context, fn func(ctx context.Context, ready ReadyFunc) error, opts ...GoOption)
StartWithReady spawns a goroutine and blocks until fn signals readiness by calling ready(). If fn returns before calling ready(), StartWithReady unblocks anyway. The ready function is safe to call multiple times. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.startwithready".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
proceed := make(chan struct{})
done := make(chan struct{})
gofuncy.StartWithReady(context.Background(), func(ctx context.Context, ready gofuncy.ReadyFunc) error {
defer close(done)
fmt.Println("initializing")
ready()
<-proceed
fmt.Println("running")
return nil
})
fmt.Println("ready")
close(proceed)
<-done
}
Output: initializing ready running
func StartWithStop ¶ added in v0.2.0
func StartWithStop(ctx context.Context, fn func(ctx context.Context, stop StopFunc) error, opts ...GoOption)
StartWithStop spawns a fire-and-forget goroutine that receives a stop function. Calling stop cancels the goroutine's context, signaling it to shut down. The stop function is safe to call multiple times. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.startwithstop".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
done := make(chan struct{})
gofuncy.StartWithStop(context.Background(), func(ctx context.Context, stop gofuncy.StopFunc) error {
defer close(done)
fmt.Println("started")
stop() // self-cancel
<-ctx.Done()
fmt.Println("stopped")
return nil
})
<-done
}
Output: started stopped
func Wait ¶
Wait spawns a goroutine with the full middleware chain and returns a wait function. Calling the wait function blocks until the goroutine completes and returns its error. The wait function is safe to call multiple times and from multiple goroutines — it always returns the same result. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.wait".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
wait := gofuncy.Wait(context.Background(), func(ctx context.Context) error {
fmt.Println("working")
return nil
})
// Do other work here while goroutine runs...
if err := wait(); err != nil {
fmt.Println("error:", err)
}
fmt.Println("done")
}
Output: working done
func WaitWithReady ¶ added in v0.2.0
func WaitWithReady(ctx context.Context, fn func(ctx context.Context, ready ReadyFunc) error, opts ...GoOption) func() error
WaitWithReady spawns a goroutine and blocks until fn signals readiness by calling ready(), then returns a wait function. If fn returns before calling ready(), WaitWithReady unblocks anyway. The wait function blocks until the goroutine completes and returns its error. Both the ready and wait functions are safe to call multiple times. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.waitwithready".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
proceed := make(chan struct{})
wait := gofuncy.WaitWithReady(context.Background(), func(ctx context.Context, ready gofuncy.ReadyFunc) error {
fmt.Println("initializing")
ready()
<-proceed
fmt.Println("done")
return nil
})
fmt.Println("ready")
close(proceed)
if err := wait(); err != nil {
fmt.Println("error:", err)
}
}
Output: initializing ready done
func WaitWithStop ¶ added in v0.2.0
func WaitWithStop(ctx context.Context, fn func(ctx context.Context, stop StopFunc) error, opts ...GoOption) func() error
WaitWithStop spawns a goroutine that receives a stop function and returns a wait function. Calling stop cancels the goroutine's context. The wait function blocks until the goroutine completes and returns its error. Both functions are safe to call multiple times. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.waitwithstop".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
wait := gofuncy.WaitWithStop(context.Background(), func(ctx context.Context, stop gofuncy.StopFunc) error {
fmt.Println("started")
stop()
<-ctx.Done()
fmt.Println("stopped")
return nil
})
err := wait()
fmt.Println("error:", err)
}
Output: started stopped error: <nil>
func WithCallerSkip ¶
func WithCallerSkip(skip int) goOnlyOpt
WithCallerSkip sets the caller skip for error reporting.
func WithChildTrace ¶
func WithChildTrace() baseOpt
WithChildTrace forces child spans instead of detached root spans. This is primarily useful with Go() to override its default detached behavior.
func WithCircuitBreaker ¶
func WithCircuitBreaker(cb *CircuitBreaker) baseOpt
WithCircuitBreaker sets a circuit breaker for the operation. The circuit breaker is stateful — create one via NewCircuitBreaker and share it across all calls to the same dependency.
func WithDetachedTrace ¶
func WithDetachedTrace() baseOpt
WithDetachedTrace creates new root spans linked to the parent span context instead of child spans. This is useful when goroutines represent independent work units (e.g., event processing) that should not be nested under the caller's trace but should still reference it.
For Go(), detached traces are the default — use WithChildTrace to opt out. For Do(), Wait(), and NewGroup(), child traces are the default.
func WithDurationHistogram ¶
func WithDurationHistogram() baseOpt
WithDurationHistogram enables the duration histogram metric.
func WithErrorHandler ¶
func WithErrorHandler(h ErrorHandler) goOnlyOpt
WithErrorHandler sets a custom error handler.
func WithFailFast ¶
func WithFailFast() groupOnlyOpt
WithFailFast configures the Group to cancel remaining functions on first error.
func WithFallback ¶
func WithFallback(fn func(ctx context.Context, err error) error, opts ...FallbackOption) baseOpt
WithFallback sets a fallback function that is called when the operation fails. The fallback receives the original error and may return nil to suppress it or a different error.
func WithLimit ¶
func WithLimit(n int) groupOnlyOpt
WithLimit sets the maximum number of concurrently executing functions in a Group.
func WithLimiter ¶
WithLimiter sets a shared weighted semaphore for concurrency control.
func WithLogger ¶
WithLogger configures the logger for the operation.
func WithMeterProvider ¶
func WithMeterProvider(mp metric.MeterProvider) baseOpt
WithMeterProvider sets a custom meter provider.
func WithMiddleware ¶
func WithMiddleware(m ...Middleware) baseOpt
WithMiddleware appends middleware to the operation's middleware chain.
func WithName ¶ added in v0.2.0
func WithName(name string) baseOpt
WithName sets the routine name used for context injection, metrics, and tracing. When omitted, each function uses a low-cardinality default (e.g. "gofuncy.go", "gofuncy.do").
func WithRetry ¶
func WithRetry(maxAttempts int, opts ...RetryOption) baseOpt
WithRetry configures automatic retry with the given maximum attempts. maxAttempts is the total number of attempts (1 = no retry, 3 = initial + 2 retries).
func WithStallHandler ¶
func WithStallHandler(h StallHandler) baseOpt
WithStallHandler sets a custom callback for stall detection. If not set, stalls are logged via slog.
func WithStallThreshold ¶
WithStallThreshold enables stall detection. If a goroutine runs longer than the threshold, a warning is logged and a metric is emitted. The goroutine is not cancelled. Use WithStallHandler to customize the callback.
func WithTimeout ¶
WithTimeout sets a per-invocation timeout. When combined with WithRetry, each retry attempt gets its own fresh deadline.
func WithTracerProvider ¶
func WithTracerProvider(tp trace.TracerProvider) baseOpt
WithTracerProvider sets a custom tracer provider.
func WithoutActiveUpDownCounter ¶
func WithoutActiveUpDownCounter() baseOpt
WithoutActiveUpDownCounter disables the active up-down counter metric.
func WithoutErrorCounter ¶
func WithoutErrorCounter() baseOpt
WithoutErrorCounter disables the error counter metric.
func WithoutStartedCounter ¶
func WithoutStartedCounter() baseOpt
WithoutStartedCounter disables the started counter metric.
func WithoutTracing ¶
func WithoutTracing() baseOpt
WithoutTracing disables tracing for the operation.
Types ¶
type Backoff ¶
Backoff returns the delay before the nth retry attempt (0-indexed).
func BackoffConstant ¶
BackoffConstant returns a Backoff that always waits the same duration.
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker holds the state for a circuit breaker instance. It is safe for concurrent use and should be shared across all calls to the same dependency.
func NewCircuitBreaker ¶
func NewCircuitBreaker(opts ...CircuitBreakerOption) *CircuitBreaker
NewCircuitBreaker creates a new CircuitBreaker with the given options.
type CircuitBreakerOption ¶
type CircuitBreakerOption func(*circuitBreakerConfig)
CircuitBreakerOption configures circuit breaker behavior.
func CircuitBreakerCooldown ¶
func CircuitBreakerCooldown(d time.Duration) CircuitBreakerOption
CircuitBreakerCooldown sets the duration the circuit stays open before allowing a probe request. Defaults to 30s.
func CircuitBreakerIf ¶
func CircuitBreakerIf(fn func(error) bool) CircuitBreakerOption
CircuitBreakerIf sets a custom function to determine whether an error counts as a failure. By default, all errors except context errors and panics count.
func CircuitBreakerOnStateChange ¶
func CircuitBreakerOnStateChange(fn func(from, to CircuitState)) CircuitBreakerOption
CircuitBreakerOnStateChange sets a callback invoked when the circuit transitions between states.
func CircuitBreakerThreshold ¶
func CircuitBreakerThreshold(n int) CircuitBreakerOption
CircuitBreakerThreshold sets the number of consecutive failures before the circuit opens. Defaults to 5.
type CircuitState ¶
type CircuitState int
CircuitState represents the current state of a circuit breaker.
const ( // CircuitClosed is the normal operating state where requests pass through. CircuitClosed CircuitState = iota // CircuitOpen is the state where requests are rejected immediately. CircuitOpen // CircuitHalfOpen is the state where a single probe request is allowed. CircuitHalfOpen )
func (CircuitState) String ¶
func (s CircuitState) String() string
String implements fmt.Stringer for CircuitState.
type Context ¶
Context wraps a standard context with helper methods for accessing gofuncy routine name and parent information.
func Ctx ¶
Ctx wraps a context.Context with gofuncy helper methods.
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
done := make(chan struct{})
gofuncy.Go(context.Background(), func(ctx context.Context) error {
defer close(done)
fmt.Println("name:", gofuncy.NameFromContext(ctx))
return nil
}, gofuncy.WithName("worker"))
<-done
}
Output: name: worker
type ErrorHandler ¶
ErrorHandler is a callback for handling errors from fire-and-forget goroutines.
type FallbackOption ¶
type FallbackOption func(*fallbackConfig)
FallbackOption configures fallback behavior.
func FallbackIf ¶
func FallbackIf(fn func(error) bool) FallbackOption
FallbackIf sets a custom function to determine whether an error should trigger the fallback. By default, all errors except context errors and panics trigger the fallback.
type GoOption ¶
type GoOption interface {
// contains filtered or unexported methods
}
GoOption configures Go() and Group.Add() calls.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group manages a set of concurrently executing functions with shared lifecycle control.
func NewGroup ¶
func NewGroup(ctx context.Context, opts ...GroupOption) *Group
NewGroup creates a new Group with the given context and options. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.group".
Example ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
g := gofuncy.NewGroup(context.Background())
g.Add(func(ctx context.Context) error {
fmt.Println("a")
return nil
})
g.Add(func(ctx context.Context) error {
fmt.Println("b")
return nil
})
if err := g.Wait(); err != nil {
fmt.Println("error:", err)
}
}
Output: a b
Example (FailFast) ¶
package main
import (
"context"
"fmt"
"github.com/foomo/gofuncy"
)
func main() {
g := gofuncy.NewGroup(context.Background(),
gofuncy.WithFailFast(),
gofuncy.WithLimit(2),
)
g.Add(func(ctx context.Context) error {
fmt.Println("ok")
return nil
})
g.Add(func(ctx context.Context) error {
return fmt.Errorf("something went wrong")
})
if err := g.Wait(); err != nil {
fmt.Println("group error:", err)
}
}
Output: ok group error: something went wrong
type GroupOption ¶
type GroupOption interface {
// contains filtered or unexported methods
}
GroupOption configures NewGroup() calls.
type Middleware ¶
Middleware wraps a Func to add cross-cutting behavior.
func Fallback ¶
func Fallback(fn func(ctx context.Context, err error) error, opts ...FallbackOption) Middleware
Fallback returns a Middleware that calls fn when the wrapped function returns an error, allowing graceful degradation. The fallback function receives the original context and error, and may return nil to suppress the error or a different error. By default, context errors and panics bypass the fallback; use FallbackIf to customize which errors trigger it.
func Retry ¶
func Retry(maxAttempts int, opts ...RetryOption) Middleware
Retry returns a Middleware that retries the wrapped function up to maxAttempts times total (1 = no retry, 3 = initial + up to 2 retries).
type PanicError ¶
PanicError wraps a recovered panic value with its stack trace.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
Error implements the error interface for PanicError.
func (*PanicError) Unwrap ¶
func (e *PanicError) Unwrap() error
Unwrap returns the underlying error if the panic value implements error.
type ReadyFunc ¶ added in v0.2.0
type ReadyFunc func()
ReadyFunc signals that a goroutine has completed initialization. Safe to call multiple times.
type RetryOption ¶
type RetryOption func(*retryConfig)
RetryOption configures retry behavior.
func RetryBackoff ¶
func RetryBackoff(b Backoff) RetryOption
RetryBackoff sets a custom backoff strategy.
func RetryIf ¶
func RetryIf(fn func(error) bool) RetryOption
RetryIf sets a custom function to determine whether an error is retryable.
func RetryOnRetry ¶
func RetryOnRetry(fn func(ctx context.Context, attempt int, err error)) RetryOption
RetryOnRetry sets a callback invoked before each retry attempt. The attempt parameter is 1-indexed (1 = first retry).
type StallHandler ¶
StallHandler is called when a goroutine exceeds its stall threshold. The threshold parameter is the configured stall threshold duration.
type StopFunc ¶ added in v0.2.0
type StopFunc func()
StopFunc cancels a goroutine's context, signaling it to shut down. Safe to call multiple times.
func GoWithCancel ¶ added in v0.2.0
GoWithCancel spawns a goroutine and returns a stop function. Calling stop cancels the goroutine's context, signaling it to shut down. The stop function is safe to call multiple times. Use WithName to set a custom metric/tracing label; defaults to "gofuncy.gowithcancel".
Example ¶
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/foomo/gofuncy"
)
func main() {
var running atomic.Bool
stop := gofuncy.GoWithCancel(context.Background(), func(ctx context.Context) error {
running.Store(true)
<-ctx.Done()
running.Store(false)
return nil
})
// Goroutine is running...
time.Sleep(10 * time.Millisecond)
fmt.Println("running:", running.Load())
stop()
time.Sleep(10 * time.Millisecond)
fmt.Println("running:", running.Load())
}
Output: running: true running: false
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package channel provides a generic, observable channel with optional OpenTelemetry metrics and tracing for send operations.
|
Package channel provides a generic, observable channel with optional OpenTelemetry metrics and tracing for send operations. |
|
Package semconv defines OpenTelemetry semantic convention attribute keys used by gofuncy for metrics and traces.
|
Package semconv defines OpenTelemetry semantic convention attribute keys used by gofuncy for metrics and traces. |
|
gofuncyconv
Package gofuncyconv provides typed OpenTelemetry metric instrument wrappers for gofuncy's goroutine, group, channel, and message metrics.
|
Package gofuncyconv provides typed OpenTelemetry metric instrument wrappers for gofuncy's goroutine, group, channel, and message metrics. |