Documentation
ΒΆ
Overview ΒΆ
Package sync provides small concurrency helpers.
This module is github.com/alexfalkowski/go-sync and exposes package sync.
Overview ΒΆ
The package contains:
- Convenience aliases for common synchronization primitives and atomics.
- Hook-based helpers for running an operation with centralized error handling.
- Wait and Timeout helpers for coordinating an operation with a timeout.
- Worker: a bounded scheduler for running operations concurrently.
- Future: typed asynchronous operations with context-aware waiting.
- Group helpers built on errgroup, errors.Join, and singleflight.
- Typed wrappers around sync.Pool, sync.Map, and sync/atomic.Value.
- BufferPool: a convenience pool for bytes.Buffer.
The package is intentionally small. Most types are either thin wrappers over the standard library or type aliases for widely used synchronization helpers.
Aliases ΒΆ
Once, Mutex, RWMutex, and WaitGroup are aliases for their counterparts in the standard library sync package.
Int32, Int64, Uint32, Uint64, Uintptr, Bool, and Pointer[T] are aliases for atomic types from sync/atomic.
ErrorGroup is an alias for errgroup.Group. ErrorsGroup runs functions concurrently and returns all non-nil errors joined with errors.Join.
AnyPool, AnyMap, AnyValue, AnySingleFlightGroup, and AnySingleFlightResult are aliases for the non-generic sync.Pool, sync.Map, atomic.Value, singleflight.Group, and singleflight.Result; use Pool[T], Map[K,V], Value[T], SingleFlightGroup[T], and SingleFlightResult[T] for the typed wrappers.
Hooks ΒΆ
Many APIs accept a Hook. Hook.OnRun is the operation to execute and is required. Hook.OnError is optional and centralizes error handling; when set, it is invoked only when OnRun returns a non-nil error. If OnError is nil, errors are returned (or ignored) as described by the calling API. Hook.OnRun is validated before timeout or context-cancellation shortcuts, so helpers return ErrNoOnRunProvided first when OnRun is nil. Hook callbacks must not panic. Helpers do not recover panics from Hook.OnRun or Hook.OnError; a panic is not routed through Hook.OnError and is not returned as an error.
Wait and Timeout return the result of hook.Error when the operation finishes before their own deadline logic wins the race. Worker never returns handler errors from Schedule or TrySchedule; it only invokes hook.Error for side effects.
Timeouts ΒΆ
There are two timeout-related helpers with different semantics:
Wait runs hook.OnRun and waits up to the provided timeout for it to complete. After Hook.OnRun validation, if the timeout expires (or the provided context is canceled) first, Wait returns nil without waiting for OnRun to finish. This makes Wait a βbest effortβ coordination helper rather than a cancellation mechanism. A non-positive timeout behaves the same way and returns nil without invoking Hook.OnRun.
Timeout runs hook.OnRun using a derived context with the provided timeout. After Hook.OnRun validation, if the contextβs deadline expires (or it is canceled) first, Timeout returns the derived context's cancellation cause (typically ErrTimeout, context.Canceled, or a parent-provided cause). A non-positive timeout produces an already-expired derived context, so Timeout returns ErrTimeout without invoking Hook.OnRun.
In both helpers, returning from Wait or Timeout does not forcibly stop the goroutine running Hook.OnRun. If OnRun ignores context cancellation, it may continue running in the background even after the helper has returned.
In both cases, if Hook.OnRun is nil, the functions return ErrNoOnRunProvided.
Worker ΒΆ
Worker schedules hook.OnRun to run asynchronously while bounding concurrency. Schedule blocks until a slot is acquired or the provided context is done, and is context-only: it does not derive or bound any deadline itself. To bound the wait for a slot, pass a ctx with a deadline; to give the handler its own run budget starting when it actually begins, wrap ctx inside OnRun. TrySchedule attempts to schedule only if capacity is available immediately and returns ErrWorkerFull otherwise. Errors returned by OnRun are routed to hook.OnError (if set) and are not returned by either scheduling method. Use Worker.Wait to wait for all scheduled handlers to finish, or return early with the provided context's cancellation cause if the handlers have not finished first.
The zero value of Worker is not ready for use; construct one with NewWorker. A Worker must not be copied after first use; pass and store *Worker values.
Future ΒΆ
Async starts a typed operation in a new goroutine and returns a Future for its result. Future caches the operation's value and error, so Await can be called repeatedly by one or more callers after completion.
The context passed to Async is the operation's work context. Async invokes the operation even when that context is already done; the operation is responsible for observing cancellation. The context passed to Future.Await controls only how long that caller waits; cancellation of the await context does not cancel the operation. A later Await can still retrieve the eventual result. When await cancellation is selected, Await checks completion once more, so a result published by that check wins; otherwise it returns the await context's cause.
Future does not recover panics from the operation. The operation callback must not panic.
Groups ΒΆ
ErrorsGroup runs functions concurrently and waits for all of them to finish. Wait returns all non-nil errors joined with errors.Join in the order the functions were passed to Go. ErrorsGroup retains recorded errors for its lifetime, so use a fresh ErrorsGroup for each independent batch of work. SetLimit(n) optionally bounds how many functions run concurrently; a negative n, and the zero value, mean unbounded. TryGo starts a function only if a concurrency slot is currently free, returning false without starting it otherwise. Start the first function before calling Wait for an empty group, and wait for a batch to finish before starting the next independent batch. Do not copy an ErrorsGroup after first use.
SingleFlightGroup[T] is a generic wrapper around singleflight.Group. Its zero value is ready for use. Do returns typed values directly, while DoChan returns a channel of typed SingleFlightResult[T] values for select-based workflows. Both methods preserve singleflight's shared-result behavior. When T is an interface type and the function returns a nil interface value, they expose that result as the zero value of T. Do not copy a SingleFlightGroup[T] after first use.
Typed wrappers ΒΆ
Pool[T] is a typed wrapper around sync.Pool. Its zero value is ready for use. NewPool[T] returns a pointer with the default new(T) constructor. Set Pool.New when pooled values need custom initialization; if Pool.New is nil, Get allocates new(T) when the pool is empty.
BufferPool is a convenience wrapper over Pool[bytes.Buffer]. Unlike Pool[T], its zero value is not ready for use; construct one with NewBufferPool.
Value[T] is a typed wrapper around atomic.Value. Its zero value is ready for use. Load and Swap return the zero value of T if no value has been stored yet. When T is an interface type, storing a nil interface value has the same behavior as atomic.Value.Store(nil) and panics. Do not copy a Value after first use.
Map[K,V] is a typed wrapper around sync.Map. Its zero value is ready for use. If K is an interface type and a nil interface key is stored, Range exposes that entry's key as the zero value of K. If V is an interface type and a nil interface value is stored, methods that return values expose that entry as the zero value of V. Range follows the same semantics as sync.Map.Range and does not provide a consistent snapshot. Do not copy a Map after first use.
Index ΒΆ
- Variables
- func IsTimeoutError(err error) bool
- func Timeout(ctx context.Context, timeout time.Duration, hook Hook) error
- func Wait(ctx context.Context, timeout time.Duration, hook Hook) error
- type AnyMap
- type AnyPool
- type AnySingleFlightGroup
- type AnySingleFlightResult
- type AnyValue
- type Bool
- type BufferPool
- type ErrorGroup
- type ErrorHandler
- type ErrorsGroup
- type Future
- type Handler
- type Hook
- type Int32
- type Int64
- type Map
- func (m *Map[K, V]) Clear()
- func (m *Map[K, V]) CompareAndDelete(key K, old V) bool
- func (m *Map[K, V]) CompareAndSwap(key K, old, new V) bool
- func (m *Map[K, V]) Delete(key K)
- func (m *Map[K, V]) Load(key K) (V, bool)
- func (m *Map[K, V]) LoadAndDelete(key K) (V, bool)
- func (m *Map[K, V]) LoadOrStore(key K, value V) (V, bool)
- func (m *Map[K, V]) Range(f func(key K, value V) bool)
- func (m *Map[K, V]) Store(key K, value V)
- func (m *Map[K, V]) Swap(key K, value V) (V, bool)
- type Mutex
- type Once
- type Pointer
- type Pool
- type RWMutex
- type SingleFlightGroup
- type SingleFlightResult
- type Uint32
- type Uint64
- type Uintptr
- type Value
- type WaitGroup
- type Worker
Examples ΒΆ
Constants ΒΆ
This section is empty.
Variables ΒΆ
var ErrNoOnRunProvided = errors.New("no OnRun handler provided")
ErrNoOnRunProvided is returned when Hook.OnRun is nil.
var ErrTimeout = fmt.Errorf("timeout: %w", context.DeadlineExceeded)
ErrTimeout is the timeout cause used by derived contexts in this package.
It wraps context.DeadlineExceeded, so errors.Is matches both values.
var ErrWorkerFull = errors.New("worker has no available slot")
ErrWorkerFull is returned by Worker.TrySchedule when no concurrency slot is available immediately.
Functions ΒΆ
func IsTimeoutError ΒΆ
IsTimeoutError reports whether err matches ErrTimeout or context.DeadlineExceeded.
It uses errors.Is, so wrapped deadline-exceeded errors also report true.
func Timeout ΒΆ
Timeout runs hook.OnRun with a derived context that has the given timeout.
Timeout differs from Wait in two key ways:
- Cancellation is propagated to OnRun by passing a derived context created by context.WithTimeoutCause. Well-behaved OnRun implementations should observe ctx.Done() and exit promptly.
- Timeout reports timeout/cancellation by returning context.Cause when the derived context becomes done before OnRun completes.
If OnRun completes before the derived context is done, Timeout returns hook.Error(ctx, hook.OnRun(ctx)) where ctx is the derived context.
Even after receiving the OnRun result, Timeout re-checks the derived context. If it is done at that point, Timeout returns context.Cause.
After OnRun validation, if the input ctx is already done on entry, Timeout returns its cancellation cause without invoking OnRun. If timeout <= 0, Timeout returns ErrTimeout without invoking OnRun.
As with Wait, returning from Timeout does not forcibly stop the goroutine running OnRun. If OnRun ignores ctx.Done(), it may continue running in the background. Hook.OnError may still run there, but Timeout discards its return value once the derived context has already ended. Timeout does not recover panics from OnRun or OnError; see Hook.
If hook.OnRun is nil, Timeout returns ErrNoOnRunProvided before checking whether ctx is done or timeout <= 0.
Example ΒΆ
package main
import (
"context"
"fmt"
"time"
"github.com/alexfalkowski/go-sync"
)
func main() {
err := sync.Timeout(context.Background(), 10*time.Millisecond, sync.Hook{
OnRun: func(ctx context.Context) error {
<-ctx.Done()
return context.Cause(ctx)
},
})
fmt.Println(sync.IsTimeoutError(err))
}
Output: true
func Wait ΒΆ
Wait runs hook.OnRun and waits up to timeout for it to complete.
Wait is a βbest effortβ waiting helper. It does not cancel the work started by OnRun. Instead, it starts OnRun asynchronously and then waits for whichever happens first:
- OnRun completes: Wait returns hook.Error(ctx, hook.OnRun(ctx)).
- The timeout elapses: Wait returns nil immediately.
- ctx is done: Wait returns nil immediately.
Even after receiving the OnRun result, Wait re-checks timeout/context state before returning. If timeout has elapsed or ctx is done at that point, Wait returns nil.
Important: if the timeout elapses or ctx becomes done, Wait returns without waiting for OnRun to finish. The OnRun goroutine may continue running in the background. If OnRun later returns an error, Hook.OnError may still run in that goroutine, but Wait discards the final return value. Wait does not recover panics from OnRun or OnError; see Hook.
After OnRun validation, if ctx is already done on entry (or timeout <= 0), Wait returns nil without invoking OnRun.
If hook.OnRun is nil, Wait returns ErrNoOnRunProvided before checking whether ctx is done or timeout <= 0.
Example ΒΆ
package main
import (
"context"
"fmt"
"time"
"github.com/alexfalkowski/go-sync"
)
func main() {
err := sync.Wait(context.Background(), time.Second, sync.Hook{
OnRun: func(context.Context) error {
return nil
},
})
fmt.Println(err == nil)
}
Output: true
Types ΒΆ
type AnyMap ΒΆ added in v1.29.0
AnyMap is an alias for sync.Map.
It is provided for convenience so users of this package can refer to the non-generic map without importing `sync` directly. For a typed map keyed by K with values of type V, use Map.
type AnyPool ΒΆ added in v1.29.0
AnyPool is an alias for sync.Pool.
It is provided for convenience so users of this package can refer to the standard, non-generic pool without importing `sync` directly. For a typed pool that stores and returns *T without caller-side type assertions, use Pool.
type AnySingleFlightGroup ΒΆ added in v1.29.0
type AnySingleFlightGroup = singleflight.Group
AnySingleFlightGroup is an alias for singleflight.Group.
It is provided for convenience so users of this package can refer to the non-generic singleflight group without importing `golang.org/x/sync/singleflight` directly. For a type-safe group that returns values of type T, use SingleFlightGroup.
type AnySingleFlightResult ΒΆ added in v1.29.0
type AnySingleFlightResult = singleflight.Result
AnySingleFlightResult is an alias for singleflight.Result.
It is provided for convenience so users of this package can refer to the non-generic singleflight result (as returned by [AnySingleFlightGroup.DoChan]) without importing `golang.org/x/sync/singleflight` directly. Its value field is `Val any`; for a type-safe result with a `Value T` field, use SingleFlightResult.
type AnyValue ΒΆ added in v1.29.0
AnyValue is an alias for atomic.Value.
It is provided for convenience so users of this package can refer to the non-generic atomic value without importing `sync/atomic` directly. For a typed value holding T, use Value.
type Bool ΒΆ added in v1.15.0
Bool is an alias for atomic.Bool.
It is provided for convenience so users of this package can refer to a typed atomic boolean without importing sync/atomic directly.
type BufferPool ΒΆ
type BufferPool struct {
// contains filtered or unexported fields
}
BufferPool provides pooled bytes.Buffer values.
Buffers returned by BufferPool.Get should be considered temporarily borrowed by the caller. Return them to the pool via BufferPool.Put when finished to enable reuse and reduce allocations.
The zero value is not ready for use.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
pool := sync.NewBufferPool()
buffer := pool.Get()
defer pool.Put(buffer)
buffer.WriteString("hello")
copy := pool.Copy(buffer)
fmt.Println(string(copy))
}
Output: hello
func NewBufferPool ΒΆ
func NewBufferPool() *BufferPool
NewBufferPool returns a pointer to an initialized BufferPool.
The returned pool is ready for use and is backed by a generic Pool of bytes.Buffer values.
The zero value of BufferPool is not ready for use; construct one with NewBufferPool.
func (*BufferPool) Copy ΒΆ added in v1.1.0
func (p *BufferPool) Copy(buffer *bytes.Buffer) []byte
Copy returns a copy of the buffer contents as a new byte slice.
The returned slice does not alias the buffer's underlying array, so it is safe to keep after the buffer is returned to the pool.
If buffer is nil, Copy returns nil.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
pool := sync.NewBufferPool()
fmt.Println(pool.Copy(nil) == nil)
}
Output: true
func (*BufferPool) Get ΒΆ
func (p *BufferPool) Get() *bytes.Buffer
Get returns a buffer from the pool.
The returned buffer is empty. New buffers start zeroed, and BufferPool.Put resets buffers before returning them to the pool.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
pool := sync.NewBufferPool()
buffer := pool.Get()
defer pool.Put(buffer)
buffer.WriteString("aaa")
fmt.Println(buffer.String())
}
Output: aaa
func (*BufferPool) Put ΒΆ
func (p *BufferPool) Put(buffer *bytes.Buffer)
Put resets buffer and puts it back into the pool.
If buffer is nil, Put is a no-op.
type ErrorGroup ΒΆ added in v1.9.0
ErrorGroup is an alias for errgroup.Group.
It is provided for convenience so users of this package can refer to an errgroup without importing `golang.org/x/sync/errgroup` directly.
Note: this is a type alias, not a wrapper. All behavior, including how errors are captured and how `Wait` behaves, is defined by `errgroup.Group`.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.ErrorGroup
g.Go(func() error { return nil })
g.Go(func() error { return context.Canceled })
fmt.Println(g.Wait() != nil)
}
Output: true
type ErrorHandler ΒΆ
ErrorHandler is the signature for Hook.OnError.
It is invoked only when a non-nil error is returned from Hook.OnRun. If the ErrorHandler returns a different error, Hook.Error returns that error. Whether that value reaches the API caller depends on the helper invoking the hook.
type ErrorsGroup ΒΆ added in v1.24.0
type ErrorsGroup struct {
// contains filtered or unexported fields
}
ErrorsGroup runs functions concurrently and joins all returned errors.
Unlike ErrorGroup, which returns the first non-nil error reported by an errgroup, ErrorsGroup records every non-nil error and returns them from ErrorsGroup.Wait using errors.Join.
ErrorsGroup retains recorded errors for its lifetime. Use a fresh ErrorsGroup for each independent batch of work.
By default, ErrorsGroup places no limit on the number of concurrently running functions started by ErrorsGroup.Go. Call ErrorsGroup.SetLimit to cap concurrency.
Functions passed to ErrorsGroup.Go must not panic; panics are not recovered or joined into the error returned by ErrorsGroup.Wait.
The zero value of ErrorsGroup is ready for use.
An ErrorsGroup must not be copied after first use.
Example ΒΆ
package main
import (
"errors"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.ErrorsGroup
first := errors.New("first")
second := errors.New("second")
g.Go(func() error { return first })
g.Go(func() error { return second })
err := g.Wait()
fmt.Println(errors.Is(err, first), errors.Is(err, second))
}
Output: true true
func (*ErrorsGroup) Go ΒΆ added in v1.24.0
func (g *ErrorsGroup) Go(f func() error)
Go calls the given function in a new goroutine.
The first call to ErrorsGroup.Wait blocks until all functions started by Go have returned. Non-nil errors are joined in the order the functions were passed to Go, not the order they complete.
If a limit is set via ErrorsGroup.SetLimit, Go blocks until a concurrency slot is available before starting the function.
Go inherits sync.WaitGroup.Go sequencing constraints: start the first function before calling Wait for an empty group, and wait for a batch to finish before starting the next independent batch.
func (*ErrorsGroup) SetLimit ΒΆ added in v1.31.0
func (g *ErrorsGroup) SetLimit(n int)
SetLimit limits the number of concurrently running functions started by ErrorsGroup.Go to at most n. A negative n means unbounded, which is also the default for a zero-value ErrorsGroup. A limit of zero prevents any subsequent call to Go from starting its function, matching errgroup.Group.SetLimit.
Mirroring errgroup.Group.SetLimit, SetLimit must not be called while any function started by Go is still running; a limit set before Go is first called applies to every subsequent call to Go.
Example ΒΆ
package main
import (
"errors"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.ErrorsGroup
g.SetLimit(1)
first := errors.New("first")
second := errors.New("second")
g.Go(func() error { return first })
g.Go(func() error { return second })
err := g.Wait()
fmt.Println(errors.Is(err, first), errors.Is(err, second))
}
Output: true true
func (*ErrorsGroup) TryGo ΒΆ added in v1.33.0
func (g *ErrorsGroup) TryGo(f func() error) bool
TryGo calls the given function in a new goroutine only if a concurrency slot is currently free.
If a limit is set via ErrorsGroup.SetLimit and no slot is free, TryGo returns false without starting f. Otherwise TryGo returns true; f runs like one started by ErrorsGroup.Go, and its error, if any, is joined into the result of a later ErrorsGroup.Wait. When no limit is set, TryGo always starts f, matching errgroup.Group.TryGo.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.ErrorsGroup
g.SetLimit(1)
release := make(chan struct{})
started := g.TryGo(func() error {
<-release
return nil
})
rejected := g.TryGo(func() error { return nil })
close(release)
_ = g.Wait()
fmt.Println(started, rejected)
}
Output: true false
func (*ErrorsGroup) Wait ΒΆ added in v1.24.0
func (g *ErrorsGroup) Wait() error
Wait blocks until all functions started by ErrorsGroup.Go have returned, then returns all non-nil errors joined with errors.Join.
Wait does not clear recorded errors. A later call to Wait on the same ErrorsGroup can return errors from earlier Go calls.
type Future ΒΆ added in v1.30.0
type Future[T any] struct { // contains filtered or unexported fields }
Future represents the eventual result of an asynchronous operation.
A Future is safe for concurrent use. Its result is cached, so Await can be called repeatedly by one or more callers after the operation completes. The zero value is not ready for use; construct a Future with Async. Do not copy a Future after first use; pass and store *Future values.
func Async ΒΆ added in v1.30.0
Async starts fn in a new goroutine and returns a Future for its result.
ctx is passed to fn and controls the operation. Async invokes fn even if ctx is already done, so fn is responsible for observing cancellation. Async does not cancel the operation when a caller's Await context is done. If fn returns an error, Async caches that error and returns it from every subsequent Await call. fn must be non-nil and must not panic; Async does not recover panics from fn.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
future := sync.Async(context.Background(), func(context.Context) (int, error) {
return 42, nil
})
value, err := future.Await(context.Background())
fmt.Println(value, err == nil)
}
Output: 42 true
func (*Future[T]) Await ΒΆ added in v1.30.0
Await waits for the Future to complete or for ctx to be done.
When ctx.Done is selected, Await checks completion once more before returning. A result published by that check wins; otherwise Await returns ctx's cancellation cause without canceling the operation. A later Await can still retrieve the operation's eventual result. Once the operation completes, its cached value and error are returned to every caller.
Example ΒΆ
package main
import (
"context"
"errors"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
release := make(chan struct{})
future := sync.Async(context.Background(), func(context.Context) (int, error) {
<-release
return 42, nil
})
// Awaiting with an already-canceled context returns the cause without
// canceling the still-running operation.
canceled, cancel := context.WithCancel(context.Background())
cancel()
_, err := future.Await(canceled)
fmt.Println(errors.Is(err, context.Canceled))
// After the operation finishes, a later Await retrieves the cached result.
close(release)
value, err := future.Await(context.Background())
fmt.Println(value, err == nil)
}
Output: true 42 true
type Handler ΒΆ
Handler is the signature for Hook.OnRun.
The provided context.Context is the context used by the operation invoking the hook (for example, the original ctx passed to Wait, or the derived timeout context created by Timeout).
type Hook ΒΆ
type Hook struct {
OnRun Handler
OnError ErrorHandler
}
Hook bundles handlers used by helpers in this package.
The helpers in this package call Hook.OnRun to perform work and then pass the returned error to Hook.Error, which applies Hook.OnError if configured.
Hook.OnRun must be non-nil; otherwise operations return ErrNoOnRunProvided. Helpers validate OnRun before applying context or timeout shortcut paths. Hook callbacks must not panic. Helpers do not recover panics from OnRun or OnError; a panic is not routed through OnError and is not returned as an error.
Whether the value returned from Hook.Error is observed depends on the calling helper:
- Wait returns it only if OnRun finishes before timeout/cancellation wins.
- Timeout returns it only if OnRun finishes before the derived context ends.
- Worker.Schedule and Worker.TrySchedule never return it; handler errors are only observed via Hook.OnError side effects.
func (*Hook) Error ΒΆ
Error applies Hook.OnError when err is non-nil and OnError is set.
Otherwise, it returns err unchanged. A nil err always yields nil.
Example ΒΆ
package main
import (
"context"
"errors"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
runErr := errors.New("boom")
hook := sync.Hook{
OnError: func(_ context.Context, err error) error {
return fmt.Errorf("wrapped: %w", err)
},
}
err := hook.Error(context.Background(), runErr)
fmt.Println(errors.Is(err, runErr))
}
Output: true
type Int32 ΒΆ added in v1.13.0
Int32 is an alias for atomic.Int32.
It is provided for convenience so users of this package can refer to a typed atomic integer without importing sync/atomic directly.
type Int64 ΒΆ added in v1.19.0
Int64 is an alias for atomic.Int64.
It is provided for convenience so users of this package can refer to a typed atomic integer without importing sync/atomic directly.
type Map ΒΆ
type Map[K comparable, V any] struct { // contains filtered or unexported fields }
Map is a typed wrapper around sync.Map.
It provides a generic API while preserving sync.Mapβs concurrency properties. This includes sync.Map's iteration semantics: Map.Range does not provide a consistent snapshot when concurrent stores and deletes are happening.
Zero value ΒΆ
The zero value is ready for use.
Missing keys vs stored zero values ΒΆ
Methods such as Map.Load, Map.LoadAndDelete, and Map.Swap return the zero value of V when a key is not present. Use the returned boolean to distinguish βnot presentβ from a stored zero value.
Nil interface keys and values ΒΆ
If K is an interface type, storing a nil interface key results in an untyped nil key being stored.
Map.Range exposes such keys as the zero value of K.
Internally, sync.Map stores values as `any`. This wrapper type-asserts stored values back to V for some operations. If V is an interface type, storing a nil interface value (for example, `var r io.Reader = nil`) results in an untyped nil being stored.
Methods that return values from the map (such as Map.Load, Map.LoadOrStore, Map.LoadAndDelete, Map.Swap, and Map.Range) treat this as the zero value of V. Use the returned booleans where available to distinguish stored zero values from absent keys.
A Map must not be copied after first use.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var m sync.Map[string, int]
m.Store("one", 1)
v, ok := m.Load("one")
fmt.Println(v, ok)
}
Output: 1 true
func NewMap ΒΆ
func NewMap[K comparable, V any]() *Map[K, V]
NewMap returns a pointer to a Map ready for use.
The zero value of Map is also ready for use; NewMap is purely optional.
func (*Map[K, V]) CompareAndDelete ΒΆ
CompareAndDelete executes the compare-and-delete operation.
It follows sync.Map.CompareAndDelete semantics. If old's dynamic type is not comparable, CompareAndDelete panics.
func (*Map[K, V]) CompareAndSwap ΒΆ
CompareAndSwap executes the compare-and-swap operation.
It follows sync.Map.CompareAndSwap semantics. If old's dynamic type is not comparable, CompareAndSwap panics.
func (*Map[K, V]) Load ΒΆ
Load returns the value stored in the map for key.
It returns the zero value of V when the key is not present; ok reports whether the key was present.
func (*Map[K, V]) LoadAndDelete ΒΆ
LoadAndDelete deletes the value for key, returning the previous value if any.
It returns the zero value of V when the key is not present; loaded reports whether the key was present.
func (*Map[K, V]) LoadOrStore ΒΆ
LoadOrStore returns the existing value for key if present.
Otherwise, it stores and returns the given value.
The returned loaded result reports whether the value was already present.
If the stored value is nil (for example, when V is an interface type and a nil value was stored), it returns the zero value of V.
func (*Map[K, V]) Range ΒΆ
Range calls f sequentially for each key and value present in the map.
It follows sync.Map.Range semantics. In particular, Range does not necessarily correspond to any consistent snapshot of the map's contents.
If f returns false, Range stops the iteration.
If a stored key is nil (for example, when K is an interface type and a nil interface key was stored), Range passes the zero value of K to f.
If a stored value is nil (for example, when V is an interface type and a nil value was stored), Range passes the zero value of V to f.
Example ΒΆ
package main
import (
"fmt"
"io"
"github.com/alexfalkowski/go-sync"
)
func main() {
var m sync.Map[fmt.Stringer, io.Reader]
var key fmt.Stringer
var r io.Reader
m.Store(key, r)
m.Range(func(key fmt.Stringer, value io.Reader) bool {
fmt.Println(key == nil, value == nil)
return true
})
}
Output: true true
type Mutex ΒΆ added in v1.9.0
Mutex is an alias of sync.Mutex.
It is provided for convenience so users of this package can refer to a mutex without importing the standard library sync package directly.
type Once ΒΆ added in v1.19.0
Once is an alias of sync.Once.
It is provided for convenience so users of this package can refer to a once value without importing the standard library sync package directly.
type Pointer ΒΆ added in v1.18.0
Pointer is an alias for atomic.Pointer.
It is provided for convenience so users of this package can refer to a typed atomic pointer without importing sync/atomic directly.
type Pool ΒΆ
type Pool[T any] struct { New func() *T // contains filtered or unexported fields }
Pool is a typed wrapper around sync.Pool.
It stores and returns pointers to T (*T) to avoid copying large values. Pool does not reset values automatically on Put. If New is non-nil, Get calls it to create a value when the pool is empty. If New is nil, Get allocates new(T) when the pool is empty.
Zero value ΒΆ
The zero value is ready for use.
Semantics ΒΆ
Pool has the same semantics as sync.Pool:
- Items may be dropped at any time by the runtime.
- Items are meant to be reused to reduce allocations, not to manage resource lifetimes.
- Values taken from the pool should be considered ephemeral and should not be assumed to be unique or to remain in the pool.
Callers are responsible for resetting any state on values before reusing them, if needed.
A Pool must not be copied after first use.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
type item struct {
id int
}
var pool sync.Pool[item]
v := pool.Get()
v.id = 10
pool.Put(v)
v2 := pool.Get()
fmt.Println(v2 != nil)
pool.Put(v2)
}
Output: true
func NewPool ΒΆ
NewPool returns a pointer to an initialized Pool for values of type T.
The returned pool creates new values on demand by allocating `new(T)` when empty.
Note: the returned Pool stores *T values. Callers should treat values obtained from Pool.Get as temporarily borrowed and return them to the pool with Pool.Put when finished.
func (*Pool[T]) Get ΒΆ
func (p *Pool[T]) Get() *T
Get returns a pointer to a T from the pool.
The returned pointer is owned by the caller until it is returned via Pool.Put. If New is set and returns nil, Get returns nil.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
type item struct {
values []string
}
pool := sync.Pool[item]{
New: func() *item {
return &item{values: make([]string, 0, 2)}
},
}
v := pool.Get()
fmt.Println(v.values == nil, cap(v.values))
pool.Put(v)
}
Output: false 2
type RWMutex ΒΆ added in v1.9.0
RWMutex is an alias of sync.RWMutex.
It is provided for convenience so users of this package can refer to a read/write mutex without importing the standard library sync package directly.
type SingleFlightGroup ΒΆ added in v1.9.0
type SingleFlightGroup[T any] struct { // contains filtered or unexported fields }
SingleFlightGroup suppresses duplicate executions of functions associated with the same key.
It is a thin, generic wrapper around singleflight.Group that provides type-safe results (via the type parameter T) while preserving singleflight semantics.
For a given key, the first caller executes the provided function and concurrent callers for the same key wait for that execution to complete and receive the same result.
The zero value of SingleFlightGroup is ready for use.
The type parameter T describes the value returned from SingleFlightGroup.Do and SingleFlightGroup.DoChan. If the function returns a non-nil error, both methods expose the zero value of T along with that error.
Implementation detail: the underlying singleflight implementation stores and returns values as `any`, so this wrapper performs a type assertion back to T. As long as the function passed to Do or DoChan returns a value of type T, the assertion will succeed.
When T is an interface type and fn returns a nil interface value, Do and DoChan expose that result as the zero value of T.
A SingleFlightGroup must not be copied after first use.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.SingleFlightGroup[int]
v, err, shared := g.Do("key", func() (int, error) {
return 42, nil
})
fmt.Println(v, err == nil, shared)
}
Output: 42 true false
func NewSingleFlightGroup ΒΆ added in v1.9.0
func NewSingleFlightGroup[T any]() *SingleFlightGroup[T]
NewSingleFlightGroup creates a pointer to a new SingleFlightGroup instance.
A SingleFlightGroup is a generic wrapper around singleflight.Group that provides type-safe results (via the type parameter T) while preserving singleflight semantics.
The zero value of SingleFlightGroup is already ready for use, so calling NewSingleFlightGroup is optional.
func (*SingleFlightGroup[T]) Do ΒΆ added in v1.9.0
func (g *SingleFlightGroup[T]) Do(key string, fn func() (T, error)) (T, error, bool)
Do executes fn for the given key, making sure that only one execution is in flight at a time for that key.
If another execution for the same key is already running, Do waits for it and returns the same results.
It returns (value, err, shared):
- value is the successful result of fn (type T), or the zero value of T if err != nil.
- err is the error returned by fn.
- shared reports whether the result was given to multiple callers.
If fn returns a nil interface value and T is an interface type, value is the zero value of T.
func (*SingleFlightGroup[T]) DoChan ΒΆ added in v1.25.0
func (g *SingleFlightGroup[T]) DoChan(key string, fn func() (T, error)) <-chan SingleFlightResult[T]
DoChan is like SingleFlightGroup.Do but returns a channel that receives the result.
The returned channel is buffered with capacity 1 and is not closed, matching singleflight.Group.DoChan. If fn returns a nil interface value and T is an interface type, SingleFlightResult.Value is the zero value of T.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var g sync.SingleFlightGroup[int]
ch := g.DoChan("key", func() (int, error) {
return 42, nil
})
result := <-ch
fmt.Println(result.Value, result.Err == nil, result.Shared)
}
Output: 42 true false
func (*SingleFlightGroup[T]) Forget ΒΆ added in v1.9.0
func (g *SingleFlightGroup[T]) Forget(key string)
Forget forgets an in-flight call for key.
Future calls to SingleFlightGroup.Do or SingleFlightGroup.DoChan with the same key will invoke their function rather than waiting for the earlier call to complete. Forget does not cancel or stop the forgotten in-flight call.
type SingleFlightResult ΒΆ added in v1.25.0
SingleFlightResult holds the result returned by SingleFlightGroup.DoChan.
Value is the successful result of the function, or the zero value of T when Err is non-nil. Shared reports whether the result was given to multiple callers.
type Uint32 ΒΆ added in v1.19.0
Uint32 is an alias for atomic.Uint32.
It is provided for convenience so users of this package can refer to a typed atomic integer without importing sync/atomic directly.
type Uint64 ΒΆ added in v1.19.0
Uint64 is an alias for atomic.Uint64.
It is provided for convenience so users of this package can refer to a typed atomic integer without importing sync/atomic directly.
type Uintptr ΒΆ added in v1.19.0
Uintptr is an alias for atomic.Uintptr.
It is provided for convenience so users of this package can refer to a typed atomic integer without importing sync/atomic directly.
type Value ΒΆ
type Value[T any] struct { // contains filtered or unexported fields }
Value is a typed wrapper around atomic.Value.
It provides a generic API while preserving the semantics and constraints of atomic.Value.
Zero value ΒΆ
The zero value is ready for use.
Unset values ΒΆ
If no value has been stored yet, Value.Load and Value.Swap return the zero value of T. Value.CompareAndSwap follows atomic.Value.CompareAndSwap: an unset Value can be initialized only by comparing against a nil interface value.
Type safety and panics ΒΆ
Internally, atomic.Value stores values as `any`. This wrapper type-asserts the stored value back to T on Load/Swap. The assertion will succeed as long as you only store values of type T in this Value.
Storing or swapping values of different concrete types in the same underlying atomic.Value has the same constraints as atomic.Value itself and may panic.
When T is an interface type, storing a nil interface value has the same behavior as atomic.Value.Store(nil) and panics.
A Value must not be copied after first use.
Example ΒΆ
package main
import (
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
var value sync.Value[int]
fmt.Println(value.Load())
value.Store(1)
fmt.Println(value.Swap(2))
}
Output: 0 1
func NewValue ΒΆ added in v0.25.0
NewValue returns a pointer to a new Value wrapper.
The returned pointer is ready for use.
func (*Value[T]) CompareAndSwap ΒΆ
CompareAndSwap executes the atomic compare-and-swap operation.
It follows atomic.Value.CompareAndSwap semantics. If old's dynamic type is not comparable, CompareAndSwap panics. As with Value.Store, interface-typed values must also satisfy atomic.Value's nil and concrete-type rules. In particular, CompareAndSwap with a nil interface value for new panics.
If no value has been stored yet, CompareAndSwap can initialize the Value only when old is a nil interface value. Comparing against T's zero value returns false when that zero value is converted to a non-nil interface, such as a zero number or typed nil pointer.
func (*Value[T]) Load ΒΆ
func (v *Value[T]) Load() T
Load returns the stored value.
If no value has been stored yet, it returns the zero value of T.
func (*Value[T]) Store ΒΆ
func (v *Value[T]) Store(value T)
Store atomically stores value.
It follows atomic.Value.Store semantics. In particular, storing a nil interface value panics, and later stores must remain compatible with the concrete type established by the first store.
func (*Value[T]) Swap ΒΆ
func (v *Value[T]) Swap(new T) T
Swap atomically stores new and returns the previous value.
If no value has been stored yet, it returns the zero value of T.
It follows atomic.Value.Swap semantics. In particular, swapping a nil interface value panics, and later swaps must remain compatible with the concrete type established by the first store or swap.
type WaitGroup ΒΆ added in v1.13.0
WaitGroup is an alias for sync.WaitGroup.
It is provided for convenience so users of this package can refer to a WaitGroup without importing `sync` directly.
type Worker ΒΆ
type Worker struct {
// contains filtered or unexported fields
}
Worker schedules handlers with a bounded level of concurrency.
Work is scheduled via Worker.Schedule or Worker.TrySchedule, and completion is observed via Worker.Wait. Scheduled handlers run asynchronously in their own goroutines.
The zero value is not ready for use. A Worker must not be copied after first use; pass and store *Worker values.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
worker := sync.NewWorker(2)
var count sync.Int32
for range 3 {
err := worker.Schedule(context.Background(), sync.Hook{
OnRun: func(context.Context) error {
count.Add(1)
return nil
},
})
if err != nil {
fmt.Println(err)
return
}
}
if err := worker.Wait(context.Background()); err != nil {
fmt.Println(err)
return
}
fmt.Println(count.Load())
}
Output: 3
func NewWorker ΒΆ
NewWorker returns a pointer to a Worker that bounds concurrent execution to count.
The worker uses a buffered channel of size count as a semaphore. A call to Worker.Schedule or Worker.TrySchedule acquires one slot before starting work and releases it when the work completes.
If count is 0, Worker.Schedule always blocks until the provided context times out or is canceled, and Worker.TrySchedule returns ErrWorkerFull immediately.
The zero value of Worker is not ready for use; construct one with NewWorker.
func (*Worker) Schedule ΒΆ
Schedule attempts to schedule hook.OnRun to run asynchronously, subject to the worker's concurrency limit.
Schedule blocks until one of the following occurs:
- A concurrency slot is acquired: Schedule starts OnRun in a goroutine and returns nil.
- ctx is done first: Schedule returns context.Cause(ctx).
The context passed to OnRun is the ctx provided to Schedule. This context is also passed to hook.OnError (via hook.Error) if OnRun returns a non-nil error. Schedule does not derive or bound any deadline itself; to bound the wait for a slot, pass a ctx with a deadline. To give the handler its own run budget starting when it actually begins, wrap ctx with context.WithTimeout (or similar) inside OnRun.
Error handling semantics:
- If hook.OnRun is nil, Schedule returns ErrNoOnRunProvided. This validation happens before the context shortcut check.
- If the input context is already done on entry, Schedule returns its cancellation cause without scheduling OnRun.
- Errors returned from OnRun are routed to hook.OnError (if set) and are not returned from Schedule. Schedule only reports errors related to scheduling (cancellation before a slot is acquired).
- Once a handler has been scheduled successfully, Schedule returns nil even if ctx later expires while the handler is still running.
- Panics from OnRun or OnError are not recovered; see Hook.
To wait for all scheduled handlers to complete, call Worker.Wait.
func (*Worker) TrySchedule ΒΆ added in v1.26.0
TrySchedule attempts to schedule hook.OnRun immediately.
If a concurrency slot is available, TrySchedule starts OnRun in a goroutine and returns nil. The context passed to OnRun is the ctx provided to TrySchedule. This context is also passed to hook.OnError (via hook.Error) if OnRun returns a non-nil error.
TrySchedule does not wait for capacity. If no concurrency slot is available immediately, it returns ErrWorkerFull without scheduling OnRun.
Error handling semantics:
- If hook.OnRun is nil, TrySchedule returns ErrNoOnRunProvided. This validation happens before context or capacity shortcut checks.
- If the input context is already done on entry, TrySchedule returns its cancellation cause without scheduling OnRun.
- Errors returned from OnRun are routed to hook.OnError (if set) and are not returned from TrySchedule. TrySchedule only reports scheduling errors.
- Once a handler has been scheduled successfully, TrySchedule returns nil even if the context is later canceled while the handler is still running.
- Panics from OnRun or OnError are not recovered; see Hook.
To wait for all scheduled handlers to complete, call Worker.Wait.
Example ΒΆ
package main
import (
"context"
"fmt"
"github.com/alexfalkowski/go-sync"
)
func main() {
worker := sync.NewWorker(1)
var count sync.Int32
err := worker.TrySchedule(context.Background(), sync.Hook{
OnRun: func(context.Context) error {
count.Add(1)
return nil
},
})
if err != nil {
fmt.Println(err)
return
}
if err := worker.Wait(context.Background()); err != nil {
fmt.Println(err)
return
}
fmt.Println(count.Load())
}
Output: 1
func (*Worker) Wait ΒΆ
Wait waits for all handlers that have been successfully scheduled to complete, or for ctx to be done first.
Wait returns nil once every scheduled handler has finished, or context.Cause(ctx) if ctx is done first. Completion is observed by a goroutine started for this call rather than a persistent signal, so this is a best-effort race: it narrows, but does not eliminate, the window where an already-done ctx wins over handlers that finished shortly before Wait was called. Wait never cancels a running handler; cancellation is controlled by the contexts provided to Worker.Schedule or Worker.TrySchedule and observed by the handlers themselves. A handler that ignores its context can keep running after Wait returns; the internal goroutine started by Wait also lives until that handler finishes, matching the lifetime of the already-outstanding work. Wait can be called multiple times; each call waits for the currently scheduled work to finish.