sync

package module
v1.24.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 9 Imported by: 12

README

Gopher CircleCI codecov Go Report Card Go Reference

🔄 go-sync

A small Go library (package sync) with focused concurrency helpers:

  • Convenience aliases for common sync primitives and typed atomics
  • Hook-driven execution (Wait, Timeout, Worker)
  • Group helpers (ErrorGroup, ErrorsGroup, SingleFlightGroup)
  • Typed wrappers for sync.Pool, sync.Map, and atomic.Value
  • A bytes.Buffer pool specialized for copy-and-reuse workflows

📦 Install

Requires Go 1.26 or newer.

go get github.com/alexfalkowski/go-sync

🧭 Package layout

The public API is intentionally small:

  • Aliases: Once, Mutex, RWMutex, WaitGroup, Int32, Int64, Uint32, Uint64, Uintptr, Bool, Pointer[T]
  • Hooks and timeout helpers: Hook, Handler, ErrorHandler, ErrNoOnRunProvided, ErrTimeout, Wait, Timeout, IsTimeoutError
  • Worker: NewWorker, Worker.Schedule, Worker.Wait
  • Groups: ErrorGroup, ErrorsGroup, NewSingleFlightGroup, SingleFlightGroup
  • Pools and wrappers: NewPool, Pool[T], NewBufferPool, BufferPool, NewValue, Value[T], NewMap, Map[K, V]

Most wrappers preserve the semantics of the standard library type they wrap while making those semantics easier to use from generic code.

🔁 Aliases

The package re-exports a few commonly used concurrency primitives and helper types for convenience:

  • Once, Mutex, RWMutex, and WaitGroup alias their counterparts in sync.
  • Int32, Int64, Uint32, Uint64, Uintptr, Bool, and Pointer[T] alias typed atomics from sync/atomic.
  • ErrorGroup aliases errgroup.Group.

These are type aliases rather than wrappers, so their behavior is exactly the same as the underlying type.

🪝 Hooks

Most execution helpers accept a sync.Hook:

  • OnRun(context.Context) error is required.
  • OnError(context.Context, error) error is optional.
  • If OnRun is nil, helpers return sync.ErrNoOnRunProvided.

OnError is only called when OnRun returns a non-nil error. If OnError returns a different error, that new error is returned.

How that returned error is observed depends on the helper:

  • Wait and Timeout return it only if OnRun finishes before their timeout/cancellation path wins.
  • Worker never returns handler errors from Schedule; use OnError for logging or side effects.
package main

import (
    "context"
    "errors"
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    hook := sync.Hook{
        OnRun: func(context.Context) error {
            return errors.New("boom")
        },
        OnError: func(_ context.Context, err error) error {
            return fmt.Errorf("wrapped: %w", err)
        },
    }

    _ = hook
}

⏱️ Wait vs Timeout

Wait and Timeout both run Hook.OnRun, but they differ:

  • Wait: best-effort wait up to timeout; returns nil on timeout/cancel and does not cancel OnRun.
  • Timeout: derives a timeout context for OnRun; returns the context cause when the context ends first (sync.ErrTimeout, context.Canceled, or a parent-provided cause).
  • If the input context is already done, Wait returns nil immediately and Timeout returns the input context's cause immediately (neither invokes OnRun).
  • If timeout <= 0, Wait returns nil immediately, while Timeout returns sync.ErrTimeout immediately.

[!IMPORTANT] Neither Wait nor Timeout forcibly stops OnRun. If the handler ignores context cancellation, it can continue running after the helper returns.

Use sync.IsTimeoutError(err) to check if an error matches sync.ErrTimeout or context.DeadlineExceeded.

⏳ Wait example (best effort)
package main

import (
    "context"
    "errors"
    "fmt"
    "time"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    err := sync.Wait(context.Background(), 10*time.Millisecond, sync.Hook{
        OnRun: func(context.Context) error {
            time.Sleep(time.Second)
            return errors.New("finished too late")
        },
    })

    // true: Wait timed out first.
    fmt.Println(err == nil)
}
🚦 Timeout example (propagated cancellation)
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))
}

👷 Worker

Worker schedules asynchronous handlers with bounded concurrency.

  • Zero value is not ready; use NewWorker(count).
  • NewWorker(count) returns a ready-to-use pointer to a worker with at most count in-flight handlers.
  • Schedule blocks until a slot is acquired or timeout/cancel happens.
  • The timeout budget starts when Schedule is called, so queue wait time and handler run time share the same deadline.
  • Schedule returns only scheduling errors (the derived context cause or ErrNoOnRunProvided).
  • If timeout <= 0, Schedule returns sync.ErrTimeout immediately and does not invoke OnRun.
  • Once a handler has been scheduled, Schedule returns nil even if that handler later observes ctx.Done().
  • Wait blocks until all successfully scheduled handlers complete.
  • If the input context is already canceled, Schedule returns the input context's cause immediately and does not schedule OnRun.

[!NOTE] Schedule reports scheduling errors only. Handler errors are routed through Hook.OnError and are not returned by Schedule.

If count == 0, scheduling always blocks until timeout/cancel.

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    worker := sync.NewWorker(4)
    defer worker.Wait()

    for i := 0; i < 3; i++ {
        job := i
        err := worker.Schedule(context.Background(), time.Second, sync.Hook{
            OnRun: func(context.Context) error {
                fmt.Println("job", job)
                return nil
            },
            OnError: func(_ context.Context, err error) error {
                log.Printf("job failed: %v", err)
                return err
            },
        })
        if err != nil {
            log.Printf("schedule failed: %v", err)
        }
    }
}

👥 Group

🧩 ErrorGroup / ErrorsGroup / WaitGroup

sync.ErrorGroup is a type alias for errgroup.Group. sync.ErrorsGroup waits for every scheduled function and returns all non-nil errors joined with errors.Join. sync.WaitGroup is a type alias for sync.WaitGroup.

package main

import (
    "errors"
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    var g sync.ErrorGroup

    g.Go(func() error { return nil })
    g.Go(func() error { return errors.New("boom") })

    fmt.Println(g.Wait() != nil)
}

Use ErrorsGroup when callers need every error rather than only the first one:

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))
}
✈️ SingleFlightGroup

SingleFlightGroup[T] deduplicates concurrent work by key.

  • Zero value is ready for use; NewSingleFlightGroup[T]() is optional and returns a ready-to-use pointer.
  • Do(key, fn) returns (value, err, shared).
  • shared == true means the result was given to multiple callers.
  • On fn error, Do returns zero T plus the error.
  • If T is an interface type and fn returns a nil interface value, Do exposes it as zero T.
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)

    g.Forget("key")
}

🏊 Pool

🧺 Generic Pool

Pool[T] is a typed wrapper around sync.Pool.

  • Stores *T values.
  • Zero value is not ready; use NewPool[T]() which returns a ready-to-use pointer.
  • Follows normal sync.Pool semantics (runtime may drop entries anytime).
  • Does not reset values automatically on Put; callers are responsible for reuse hygiene.
  • Put(nil) is a no-op.
package main

import (
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    type item struct {
        ID int
    }

    pool := sync.NewPool[item]()
    it := pool.Get()
    it.ID = 10
    pool.Put(it)

    fmt.Println("ok")
}
🧽 BufferPool

BufferPool is a convenience wrapper over Pool[bytes.Buffer].

  • Zero value is not ready; use NewBufferPool() which returns a ready-to-use pointer.
  • Get returns *bytes.Buffer.
  • Get returns an empty buffer.
  • Put resets the buffer (nil-safe no-op).
  • Copy returns a cloned []byte (non-aliasing, nil-safe).
package main

import (
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    bp := sync.NewBufferPool()
    buf := bp.Get()
    defer bp.Put(buf)

    buf.WriteString("hello")
    out := bp.Copy(buf)
    fmt.Println(string(out))
}

⚛️ Value

Value[T] is a typed wrapper around atomic.Value.

  • Zero value is ready (NewValue is optional and returns a ready-to-use pointer).
  • Load and Swap return zero T if unset.
  • Same underlying constraints as atomic.Value apply.
  • If T is an interface type, storing a nil interface value panics just like atomic.Value.Store(nil).
  • When T is an interface or any, stores must still be consistent with atomic.Value's concrete-type rules.
  • CompareAndSwap follows atomic.Value.CompareAndSwap; when T is an interface, non-comparable dynamic values in old can panic.
package main

import (
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    var v sync.Value[int]
    fmt.Println(v.Load())

    v.Store(1)
    fmt.Println(v.Swap(2))
    fmt.Println(v.CompareAndSwap(2, 3))
}

🗺️ Map

Map[K, V] is a typed wrapper around sync.Map.

  • Zero value is ready (NewMap is optional and returns a ready-to-use pointer).
  • Load, LoadOrStore, LoadAndDelete, and Swap return zero V when needed; use boolean flags to distinguish missing keys.
  • If K is an interface type and a nil interface key is stored, Range exposes it as zero K (for example, nil for interface K).
  • If V is an interface type and a nil interface value is stored, value-returning methods expose it as zero V (for example, nil for interface V).
  • Range follows sync.Map.Range semantics and does not provide a consistent snapshot during concurrent mutation.
  • Clear removes all entries.
  • CompareAndSwap / CompareAndDelete follow sync.Map comparability rules; non-comparable dynamic old values can panic.
package main

import (
    "fmt"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    m := sync.NewMap[string, int]()

    m.Store("one", 1)
    v, ok := m.Load("one")
    fmt.Println(v, ok)

    prev, loaded := m.LoadOrStore("one", 99)
    fmt.Println(prev, loaded)

    m.Range(func(k string, v int) bool {
        fmt.Println(k, v)
        return true
    })
}

Nil interface edge-case example:

package main

import (
    "fmt"
    "io"

    "github.com/alexfalkowski/go-sync"
)

func main() {
    var m sync.Map[string, io.Reader]
    var r io.Reader

    m.Store("reader", r)
    m.Range(func(_ string, value io.Reader) bool {
        fmt.Println(value == nil)
        return true
    })
}

🔎 Background / References

This library draws inspiration from:

For executable, CI-verified usage examples, see example_test.go. Those examples back the rendered package documentation on pkg.go.dev.

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.
  • 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.

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.

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; 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. 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. 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 the handler is scheduled or the provided timeout (via context.WithTimeoutCause) expires. Errors returned by OnRun are routed to hook.OnError (if set) and are not returned by Schedule. Use Worker.Wait to wait for all scheduled handlers to finish.

The zero value of Worker is not ready for use; construct one with NewWorker.

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.

SingleFlightGroup[T] is a generic wrapper around singleflight.Group. Its zero value is ready for use. Do returns a typed result and preserves singleflight's shared-result behavior. When T is an interface type and the function returns a nil interface value, Do exposes that result as the zero value of T.

Typed wrappers

Pool[T] is a typed wrapper around sync.Pool. Its zero value is not ready for use; construct one with NewPool[T].

BufferPool is a convenience wrapper over Pool[bytes.Buffer]. Its zero value is also 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.

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.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoOnRunProvided = errors.New("no OnRun handler provided")

ErrNoOnRunProvided is returned when [Hook.OnRun] is nil.

View Source
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.

Functions

func IsTimeoutError

func IsTimeoutError(err error) bool

IsTimeoutError reports whether err matches ErrTimeout or context.DeadlineExceeded.

It uses errors.Is, so wrapped deadline-exceeded errors also report true.

func Timeout

func Timeout(ctx context.Context, timeout time.Duration, hook Hook) error

Timeout runs hook.OnRun with a derived context that has the given timeout.

Timeout differs from Wait in two key ways:

  1. 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.
  2. 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.

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.

If hook.OnRun is nil, Timeout returns ErrNoOnRunProvided.

Example
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

func Wait(ctx context.Context, timeout time.Duration, hook Hook) error

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:

  1. OnRun completes: Wait returns hook.Error(ctx, hook.OnRun(ctx)).
  2. The timeout elapses: Wait returns nil immediately.
  3. 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.

If ctx is already done on entry (or timeout <= 0), Wait returns nil without invoking OnRun.

If hook.OnRun is nil, Wait returns ErrNoOnRunProvided.

Example
err := sync.Wait(context.Background(), time.Second, sync.Hook{
	OnRun: func(context.Context) error {
		return nil
	},
})

fmt.Println(err == nil)
Output:
true

Types

type Bool added in v1.15.0

type Bool = atomic.Bool

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
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 (Nil)
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
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

type ErrorGroup = errgroup.Group

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
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

type ErrorHandler func(context.Context, error) error

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.

The zero value of ErrorsGroup is ready for use.

Example
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.

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.

type Handler

type Handler func(context.Context) error

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.

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 never returns it; handler errors are only observed via [Hook.OnError] side effects.

func (*Hook) Error

func (h *Hook) Error(ctx context.Context, err error) 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.

type Int32 added in v1.13.0

type Int32 = atomic.Int32

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

type Int64 = atomic.Int64

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.

Example
var m sync.Map[string, int]
m.Store("one", 1)

v, ok := m.Load("one")
fmt.Println(v, ok)
Output:
1 true
Example (NilInterfaceValue)
var m sync.Map[string, io.Reader]
var r io.Reader
m.Store("reader", r)

m.Range(func(_ string, value io.Reader) bool {
	fmt.Println(value == nil)
	return true
})
Output:
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]) Clear

func (m *Map[K, V]) Clear()

Clear deletes all keys and values.

func (*Map[K, V]) CompareAndDelete

func (m *Map[K, V]) CompareAndDelete(key K, old V) bool

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

func (m *Map[K, V]) CompareAndSwap(key K, old, new V) bool

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]) Delete

func (m *Map[K, V]) Delete(key K)

Delete deletes the value for key.

func (*Map[K, V]) Load

func (m *Map[K, V]) Load(key K) (V, bool)

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

func (m *Map[K, V]) LoadAndDelete(key K) (V, bool)

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

func (m *Map[K, V]) LoadOrStore(key K, value V) (V, bool)

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

func (m *Map[K, V]) Range(f func(key K, value V) bool)

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.

func (*Map[K, V]) Store

func (m *Map[K, V]) Store(key K, value V)

Store sets the value for key.

If V is an interface type and value is nil, the map stores an untyped nil. Load-like methods and Range expose this as the zero value of V.

func (*Map[K, V]) Swap

func (m *Map[K, V]) Swap(key K, value V) (V, bool)

Swap swaps the value for key and returns 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.

type Mutex added in v1.9.0

type Mutex = sync.Mutex

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

type Once = sync.Once

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

type Pointer[T any] = atomic.Pointer[T]

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 {
	// 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.

Zero value

The zero value is not ready for use. Construct a Pool with NewPool.

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.

Example
type item struct {
	id int
}

pool := sync.NewPool[item]()
v := pool.Get()
v.id = 10
pool.Put(v)

v2 := pool.Get()
fmt.Println(v2 != nil)
pool.Put(v2)
Output:
true

func NewPool

func NewPool[T any]() *Pool[T]

NewPool returns a pointer to an initialized Pool for values of type T.

The 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.

func (*Pool[T]) Put

func (p *Pool[T]) Put(b *T)

Put returns b to the pool.

Callers should ensure b is in an appropriate state for reuse (for example, by resetting fields) before calling Put.

If b is nil, Put is a no-op.

type RWMutex added in v1.9.0

type RWMutex = sync.RWMutex

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. If the function returns a non-nil error, Do returns 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 returns a value of type T, the assertion will succeed.

When T is an interface type and fn returns a nil interface value, Do exposes that result as the zero value of T.

Example
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]) 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 with the same key will invoke their function rather than waiting for the earlier call to complete.

type Uint32 added in v1.19.0

type Uint32 = atomic.Uint32

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

type Uint64 = atomic.Uint64

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

type Uintptr = atomic.Uintptr

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.

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 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.

Example
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

func NewValue[T any]() *Value[T]

NewValue returns a pointer to a new Value wrapper.

The returned pointer is ready for use.

func (*Value[T]) CompareAndSwap

func (v *Value[T]) CompareAndSwap(old, new T) bool

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 concrete-type rules.

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.

type WaitGroup added in v1.13.0

type WaitGroup = sync.WaitGroup

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 and completion is observed via Worker.Wait. Scheduled handlers run asynchronously in their own goroutines.

The zero value is not ready for use.

Example
worker := sync.NewWorker(2)
var count sync.Int32

for range 3 {
	_ = worker.Schedule(context.Background(), time.Second, sync.Hook{
		OnRun: func(context.Context) error {
			count.Add(1)
			return nil
		},
	})
}

worker.Wait()
fmt.Println(count.Load())
Output:
3

func NewWorker

func NewWorker(count uint) *Worker

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 acquires one slot before starting work and releases it when the work completes.

If count is 0, scheduling will always block until the provided context times out or is canceled (because the semaphore has no capacity).

The zero value of Worker is not ready for use; construct one with NewWorker.

func (*Worker) Schedule

func (w *Worker) Schedule(ctx context.Context, timeout time.Duration, hook Hook) error

Schedule attempts to schedule hook.OnRun to run asynchronously, subject to the worker's concurrency limit.

Schedule blocks until one of the following occurs:

  1. A concurrency slot is acquired before the deadline: Schedule starts OnRun in a goroutine and returns nil.
  2. The derived timeout context is done first: Schedule returns context.Cause from that derived context.

The context passed to OnRun is a derived context created by context.WithTimeoutCause using the provided timeout. The timeout budget starts when Schedule is called, so time spent waiting for a concurrency slot and time spent running OnRun share the same deadline. This context is also passed to hook.OnError (via hook.Error) if OnRun returns a non-nil error. If OnRun ignores that context and continues running after the deadline, the goroutine may outlive the caller of Schedule until the handler eventually returns.

Error handling semantics:

  • If hook.OnRun is nil, Schedule returns ErrNoOnRunProvided.
  • If the input context is already done on entry, Schedule returns its cancellation cause without scheduling OnRun.
  • If timeout <= 0, Schedule returns ErrTimeout 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 (timeout/cancellation before a slot is acquired).
  • Once a handler has been scheduled successfully, Schedule returns nil even if the derived context later expires while the handler is still running.

To wait for all scheduled handlers to complete, call Worker.Wait.

func (*Worker) Wait

func (w *Worker) Wait()

Wait blocks until all handlers that have been successfully scheduled have completed.

It does not cancel running handlers. Cancellation is controlled by the contexts provided to Worker.Schedule and observed by the handlers themselves. Wait can be called multiple times; each call waits for the currently scheduled work to finish.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL