hof

package
v0.46.0 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 6 Imported by: 0

README

hof

Build complex functions from simple ones — compose, partially apply, or wrap with concurrency control.

// Compose at the call site when the combination is obvious
normalize := hof.Pipe(strings.TrimSpace, strings.ToLower)
slice.From(inputs).Convert(normalize)

What It Looks Like

// Partial application — fix one argument
add := func(a, b int) int { return a + b }
add5 := hof.Bind(add, 5)
slice.From(nums).Convert(add5)
// Apply separate functions to separate values
both := hof.Cross(double, toUpper)
d, u := both(5, "hello")  // 10, "HELLO"
// Equality predicate for Every/Any
allSkipped := slice.From(statuses).Every(hof.Eq(Skipped))
// Bound concurrency — at most 5 in-flight API calls
callAPI := hof.Throttle(5, fetchFromAPI)
resp, err := callAPI(ctx, url)  // blocks until a slot opens
// Bound by total cost — large items consume more budget
fetchData := hof.ThrottleWeighted(100, estimateSize, fetchFromAPI)
// Cancel remaining work on first error
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
failFast := hof.OnErr(fetchURL, func(_ error) { cancel() })
// Retry with exponential backoff — composable with Throttle
resilientFetch := hof.Retry(3, hof.ExponentialBackoff(100*time.Millisecond), nil, fetchFromAPI)
resp, err := resilientFetch(ctx, url)
// Retry only transient errors — stop immediately on permanent failures
isTransient := func(err error) bool { return !errors.Is(err, ErrNotFound) }
resilientFetch := hof.Retry(3, hof.ExponentialBackoff(100*time.Millisecond), isTransient, fetchFromAPI)
// Circuit breaker — short-circuit requests to an unhealthy dependency
b := hof.NewBreaker(hof.BreakerConfig{
    ResetTimeout: 30 * time.Second,
    ReadyToTrip:  hof.ConsecutiveFailures(5),
})
safeFetch := hof.WithBreaker(b, fetchFromAPI)
resp, err := safeFetch(ctx, url)  // returns ErrCircuitOpen when open
// Circuit breaker + retry — breaker wraps retry so it sees one logical operation
b := hof.NewBreaker(hof.BreakerConfig{
    ResetTimeout: 30 * time.Second,
    ReadyToTrip:  hof.ConsecutiveFailures(5),
})
resilient := hof.WithBreaker(b,
    hof.Retry(3, hof.ExponentialBackoff(100*time.Millisecond), nil, fetchFromAPI))
// Retry wrapping breaker — filter ErrCircuitOpen to avoid retrying open circuits
b := hof.NewBreaker(hof.BreakerConfig{
    ResetTimeout: 30 * time.Second,
    ReadyToTrip:  hof.ConsecutiveFailures(5),
})
shouldRetry := func(err error) bool { return !errors.Is(err, hof.ErrCircuitOpen) }
resilient := hof.Retry(3, hof.ExponentialBackoff(100*time.Millisecond), shouldRetry,
    hof.WithBreaker(b, fetchFromAPI))
// One breaker can protect multiple functions against the same dependency
b := hof.NewBreaker(hof.BreakerConfig{
    ResetTimeout: 10 * time.Second,
    ReadyToTrip:  hof.ConsecutiveFailures(3),
})
getUser := hof.WithBreaker(b, fetchUser)
getOrder := hof.WithBreaker(b, fetchOrder)
// Debounce — coalesce rapid calls, execute once after quiet period
d := hof.NewDebouncer(500*time.Millisecond, saveConfig)
defer d.Close()  // required — stops owner goroutine
d.Call(cfg)   // resets timer, stores latest value
d.Call(cfg2)  // replaces stored value, resets timer again
// fn fires once with cfg2 after 500ms of quiet
// MaxWait — cap how long execution can be deferred under continuous activity
d := hof.NewDebouncer(200*time.Millisecond, saveSearch, hof.MaxWait(2*time.Second))
defer d.Close()
// Even if calls arrive every 100ms, fn fires within 2s of the first call
// Flush — execute pending work synchronously
d := hof.NewDebouncer(500*time.Millisecond, saveState)
defer d.Close()
d.Call(latestState)
d.Flush()  // blocks until fn completes with latestState

hof vs lof

hof builds functions — it takes functions and returns new functions. lof is functions — it wraps Go builtins and standard library functions (len, fmt.Println) as first-class values for use in chains.

hof.Pipe builds a transform; lof.Len is a transform.

Operations

Composition

  • Pipe[A, B, C](f func(A) B, g func(B) C) func(A) C — left-to-right: Pipe(f, g)(x) = g(f(x))

Partial Application

  • Bind[A, B, C](f func(A, B) C, a A) func(B) C — fix first arg
  • BindR[A, B, C](f func(A, B) C, b B) func(A) C — fix second arg

Independent Application

  • Cross[A, B, C, D](f func(A) C, g func(B) D) func(A, B) (C, D) — apply separate fns to separate args

Building Blocks

  • Eq[T comparable](target T) func(T) bool — equality predicate factory

Concurrency Control

  • Throttle[T, R](n int, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error) — bound by call count
  • ThrottleWeighted[T, R](capacity int, cost func(T) int, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error) — bound by total cost

Side-Effect Wrappers

  • OnErr[T, R](fn func(context.Context, T) (R, error), onErr func(error)) func(context.Context, T) (R, error) — call handler with error

Retry

  • Retry[T, R](maxAttempts int, backoff Backoff, shouldRetry func(error) bool, fn) func(context.Context, T) (R, error) — retry on error with pluggable backoff and optional predicate
  • ConstantBackoff(delay time.Duration) Backoff — fixed delay between retries
  • ExponentialBackoff(initial time.Duration) Backoff — full jitter: random in [0, initial * 2^n)

Circuit Breaking

  • NewBreaker(cfg BreakerConfig) *Breaker — create a circuit breaker (3-state: closed → open → half-open → closed)
  • WithBreaker[T, R](b *Breaker, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error) — wrap fn with circuit breaker protection
  • (*Breaker).Snapshot() Snapshot — point-in-time state and metrics (State, Successes, Failures, ConsecutiveFailures, Rejected, OpenedAt)
  • ConsecutiveFailures(n int) func(Snapshot) bool — ReadyToTrip predicate: trip after n consecutive failures
  • ErrCircuitOpen — sentinel error returned when the breaker is rejecting requests (open, or half-open with probe in flight)

Debounce

  • NewDebouncer[T any](wait time.Duration, fn func(T), opts ...DebounceOption) *Debouncer[T] — create trailing-edge debouncer (must not be copied after first use)
  • (*Debouncer[T]).Call(v T) — store latest value, reset timer. If fn is running, value is queued for a fresh timer cycle after completion
  • (*Debouncer[T]).Cancel() bool — cancel pending execution (true if work was pending)
  • (*Debouncer[T]).Flush() bool — execute pending work synchronously, block until fn completes (true if fn executed, false if nothing pending or flush already waiting). When fn is running with pending work queued, blocks until both the current and pending executions complete
  • (*Debouncer[T]).Close() — discard pending work, wait for any running fn to complete, stop owner goroutine. Idempotent. After Close, Call/Cancel/Flush panic
  • MaxWait(d time.Duration) DebounceOption — cap maximum deferral under continuous activity

At most one fn execution runs at a time — executions never overlap. All methods (Call, Cancel, Flush, Close) are safe for concurrent use from multiple goroutines. Call and Cancel are safe from within fn; Flush and Close from within fn will deadlock. fn runs in a spawned goroutine (not the caller's goroutine, including during Flush).

All functions panic on nil inputs (except Retry's shouldRetry and BreakerConfig callbacks, where nil means use defaults). Throttle, ThrottleWeighted, and Retry panic on non-positive limits. ThrottleWeighted also panics per-call if cost returns a non-positive value or one exceeding capacity. ExponentialBackoff panics if initial <= 0. NewDebouncer panics if wait <= 0 or fn is nil. MaxWait panics if d < 0. NewBreaker panics if ResetTimeout <= 0. ConsecutiveFailures panics if n < 1.

All context-aware wrappers (Throttle, ThrottleWeighted, Retry, WithBreaker) return ctx.Err() on cancellation rather than blocking indefinitely. WithBreaker does not count context.Canceled as a failure; context.DeadlineExceeded counts as a failure by default (controllable via ShouldCount). Pre-cancelled or pre-expired contexts bypass breaker accounting entirely.

See pkg.go.dev for complete API documentation, the main README for installation, and lof for builtin adapters.

Documentation

Overview

Package hof provides function combinators for composition, partial application, independent application, concurrency control, side-effect wrapping, and call coalescing. Based on Stone's "Algorithms: A Functional Programming Approach" (pipe, sect, cross).

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrCircuitOpen = errors.New("hof: circuit breaker is open")

ErrCircuitOpen is returned when the circuit breaker is rejecting requests. This occurs when the breaker is open, or when half-open with a probe already in flight.

Functions

func Bind

func Bind[A, B, C any](f func(A, B) C, a A) func(B) C

Bind fixes the first argument of a binary function: Bind(f, x)(y) = f(x, y). Panics if f is nil.

Example
package main

import (
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Fix the first argument of a binary function.
	add := func(a, b int) int { return a + b }
	addFive := hof.Bind(add, 5)

	fmt.Println(addFive(3))
}
Output:

8

func BindR

func BindR[A, B, C any](f func(A, B) C, b B) func(A) C

BindR fixes the second argument of a binary function: BindR(f, y)(x) = f(x, y). Panics if f is nil.

Example
package main

import (
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Fix the second argument of a binary function.
	subtract := func(a, b int) int { return a - b }
	subtractThree := hof.BindR(subtract, 3)

	fmt.Println(subtractThree(10))
}
Output:

7

func ConsecutiveFailures added in v0.42.0

func ConsecutiveFailures(n int) func(Snapshot) bool

ConsecutiveFailures returns a ReadyToTrip predicate that trips after n consecutive failures. Panics if n < 1.

func Cross

func Cross[A, B, C, D any](f func(A) C, g func(B) D) func(A, B) (C, D)

Cross applies two functions independently to two separate arguments. Cross(f, g)(a, b) = (f(a), g(b)). Panics if f or g is nil.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Apply separate functions to separate arguments.
	double := func(n int) int { return n * 2 }
	toUpper := func(s string) string { return strings.ToUpper(s) }

	both := hof.Cross(double, toUpper)
	d, u := both(5, "hello")

	fmt.Println(d, u)
}
Output:

10 HELLO

func Eq

func Eq[T comparable](target T) func(T) bool

Eq returns a predicate that checks equality to target. T is inferred from target: hof.Eq(Skipped) returns func(Status) bool.

func MapErr added in v0.42.0

func MapErr[T, R any](fn func(context.Context, T) (R, error), mapper func(error) error) func(context.Context, T) (R, error)

MapErr wraps fn so that any non-nil error returned by fn is transformed by mapper before being returned. The result value from fn is always preserved unchanged.

Example — annotate errors from a repository call:

// annotateGetUser wraps err with get-user calling context.
annotateGetUser := func(err error) error {
    return fmt.Errorf("get user: %w", err)
}
annotated := hof.MapErr(repo.GetUser, annotateGetUser)

mapper is only called for non-nil errors. For any non-nil input, mapper must return a non-nil error; the returned function panics otherwise because MapErr cannot safely convert failure into success — the wrapped function may not define a meaningful result on error.

Composition order matters: the outer wrapper sees the inner wrapper's returned error. Use fmt.Errorf with %w to preserve error identity.

Panics at construction time if fn is nil or mapper is nil.

func OnErr

func OnErr[T, R any](fn func(context.Context, T) (R, error), onErr func(error)) func(context.Context, T) (R, error)

OnErr wraps fn so that onErr is called with the error after fn returns a non-nil error. The returned function calls fn, checks for error, calls onErr(err) if present, then returns fn's original results unchanged.

onErr must be safe for concurrent use when the returned function is called from multiple goroutines.

Panics if fn is nil or onErr is nil.

Example
package main

import (
	"context"
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	var count int

	// onErr increments the error counter.
	onErr := func(_ error) { count++ }
	// failOrDouble returns an error for negative inputs.
	failOrDouble := func(_ context.Context, n int) (int, error) {
		if n < 0 {
			return 0, fmt.Errorf("negative")
		}

		return n * 2, nil
	}

	wrapped := hof.OnErr(failOrDouble, onErr)

	r1, _ := wrapped(context.Background(), 5)
	fmt.Println(r1, count)

	r2, _ := wrapped(context.Background(), -1)
	fmt.Println(r2, count)
}
Output:

10 0
0 1

func Pipe

func Pipe[A, B, C any](f func(A) B, g func(B) C) func(A) C

Pipe composes two functions left-to-right: Pipe(f, g)(x) = g(f(x)). Panics if f or g is nil.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Compose TrimSpace then ToLower into a single transform.
	normalize := hof.Pipe(strings.TrimSpace, strings.ToLower)

	fmt.Println(normalize("  Hello World  "))
}
Output:

hello world
Example (Chaining)
package main

import (
	"fmt"
	"strconv"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Multi-step composition uses intermediate variables.
	double := func(n int) int { return n * 2 }
	addOne := func(n int) int { return n + 1 }
	toString := func(n int) string { return strconv.Itoa(n) }

	doubleAddOne := hof.Pipe(double, addOne)
	full := hof.Pipe(doubleAddOne, toString)

	fmt.Println(full(5))
}
Output:

11

func Retry

func Retry[T, R any](maxAttempts int, backoff Backoff, shouldRetry func(error) bool, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error)

Retry wraps fn to retry on error up to maxAttempts total times. The first call is immediate; backoff(0) is the delay before the first retry. Returns the result and error from the last attempt.

shouldRetry controls which errors trigger a retry. When non-nil, only errors for which shouldRetry returns true are retried; non-retryable errors are returned immediately without backoff. When nil, all errors are retried.

Context cancellation is checked before each attempt and during backoff waits. Panics if maxAttempts < 1, backoff is nil, or fn is nil.

Example
package main

import (
	"context"
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	attempts := 0

	// failThenSucceed fails twice, then succeeds.
	failThenSucceed := func(_ context.Context, n int) (int, error) {
		attempts++
		if attempts < 3 {
			return 0, fmt.Errorf("not yet")
		}

		return n * 2, nil
	}

	retried := hof.Retry(3, hof.ConstantBackoff(0), nil, failThenSucceed)
	result, err := retried(context.Background(), 5)

	fmt.Println(result, err)
}
Output:

10 <nil>

func Throttle

func Throttle[T, R any](n int, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error)

Throttle wraps fn with count-based concurrency control. At most n calls to fn execute concurrently. The returned function blocks until a slot is available, then calls fn. The returned function is safe for concurrent use from multiple goroutines. Panics if n <= 0 or fn is nil.

Example
package main

import (
	"context"
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Wrap a function so at most 3 calls run concurrently.
	// doubleIt doubles the input.
	doubleIt := func(_ context.Context, n int) (int, error) { return n * 2, nil }
	throttled := hof.Throttle(3, doubleIt)

	result, err := throttled(context.Background(), 5)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(result)
}
Output:

10

func ThrottleWeighted

func ThrottleWeighted[T, R any](capacity int, cost func(T) int, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error)

ThrottleWeighted wraps fn with cost-based concurrency control. The total cost of concurrently-executing calls never exceeds capacity. The returned function blocks until enough budget is available. The returned function is safe for concurrent use from multiple goroutines.

Token acquisition is serialized to prevent partial-acquire deadlock. This means a high-cost waiter blocks later callers even if capacity is available for them (head-of-line blocking).

Panics if capacity <= 0, cost is nil, or fn is nil. Per-call: panics if cost(t) <= 0 or cost(t) > capacity.

Example
package main

import (
	"context"
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Wrap a function so total cost of concurrent calls never exceeds 100.
	// processItem returns the item unchanged.
	processItem := func(_ context.Context, n int) (int, error) { return n, nil }
	// itemCost uses the item value as its cost.
	itemCost := func(n int) int { return n }

	throttled := hof.ThrottleWeighted(100, itemCost, processItem)

	result, err := throttled(context.Background(), 42)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(result)
}
Output:

42

func WithBreaker added in v0.42.0

func WithBreaker[T, R any](b *Breaker, fn func(context.Context, T) (R, error)) func(context.Context, T) (R, error)

WithBreaker wraps fn with circuit breaker protection from b. The returned function has the standard hof signature for composition with Retry, Throttle, and other wrappers.

If ctx is already cancelled or expired before admission, the error is returned immediately without affecting breaker state or metrics.

Context cancellation (context.Canceled) does not count as a failure. context.DeadlineExceeded counts as a failure by default (controllable via ShouldCount). Errors where ShouldCount returns false and context.Canceled do not break the consecutive-failure streak; only a success resets it.

If fn panics during a half-open probe, the breaker records a failure and reopens before the panic propagates. Panics during closed-state calls do not affect breaker state.

Panics if b or fn is nil.

Types

type Backoff

type Backoff func(n int) time.Duration

Backoff computes the delay before retry number n (0-indexed). Called between attempts: backoff(0) is the delay before the first retry.

func ConstantBackoff

func ConstantBackoff(delay time.Duration) Backoff

ConstantBackoff returns a Backoff that always waits delay.

func ExponentialBackoff

func ExponentialBackoff(initial time.Duration) Backoff

ExponentialBackoff returns a Backoff with full jitter: random in [0, initial * 2^n). Panics if initial <= 0.

type Breaker added in v0.42.0

type Breaker struct {
	// contains filtered or unexported fields
}

Breaker is a circuit breaker that tracks failures and short-circuits requests when a dependency is unhealthy. Use NewBreaker to create and WithBreaker to wrap functions for composition with Retry, Throttle, and other hof wrappers.

The breaker uses a standard three-state model:

  • Closed: requests pass through, failures are counted
  • Open: requests fail immediately with ErrCircuitOpen
  • HalfOpen: one probe request is admitted; success closes, failure reopens, uncounted error (context.Canceled or ShouldCount→false) releases the probe slot without changing state

State transitions are lazy (checked on admission, not timer-driven). One probe request is admitted in half-open; all others are rejected.

Each state transition increments an internal generation counter. Calls that complete after the breaker has moved to a new generation are silently ignored, preventing stale in-flight results from corrupting the current epoch's metrics.

A Breaker must represent a single dependency or failure domain. Sharing a breaker across unrelated dependencies causes pathological coupling: one dependency's failures can trip the breaker for all, and one dependency's successful probe can close it while others remain unhealthy.

func NewBreaker added in v0.42.0

func NewBreaker(cfg BreakerConfig) *Breaker

NewBreaker creates a circuit breaker with the given configuration. Panics if ResetTimeout <= 0.

func (*Breaker) Snapshot added in v0.42.0

func (b *Breaker) Snapshot() Snapshot

Snapshot returns a point-in-time view of the breaker's state and metrics. State is the committed state; lazy transitions (open to half-open after resetTimeout) are not reflected until the next admission check.

type BreakerConfig added in v0.42.0

type BreakerConfig struct {
	// ResetTimeout is how long the breaker stays open before allowing a probe request.
	// Must be > 0.
	ResetTimeout time.Duration

	// ReadyToTrip decides whether the breaker should open based on current metrics.
	// Called outside the internal lock after each counted failure while closed.
	// The snapshot reflects the state including the current failure.
	//
	// Under concurrency, the breaker validates that no metric mutations occurred
	// between ReadyToTrip evaluation and the trip commit. If metrics changed
	// (concurrent success or failure), the trip is aborted; the next failure
	// will re-evaluate with a fresh snapshot. This means predicates should be
	// monotone with respect to failure accumulation (e.g., >= threshold) for
	// reliable tripping under contention. Non-monotone predicates (e.g., == N)
	// may miss a trip if a concurrent mutation changes the count between
	// evaluation and commit.
	//
	// Must be side-effect-free. May be called concurrently from multiple goroutines.
	// Nil defaults to ConsecutiveFailures(5).
	ReadyToTrip func(Snapshot) bool

	// ShouldCount decides whether an error counts as a failure for trip purposes.
	// Called outside the internal lock.
	// Nil means all errors count. context.Canceled never counts regardless of this setting.
	ShouldCount func(error) bool

	// OnStateChange is called after each state transition on the normal (non-panic) path,
	// outside the internal lock. Transitions caused by panic recovery (e.g., a half-open
	// probe fn panic reopening the breaker) do not trigger the callback to avoid masking
	// the original panic.
	// Under concurrency, callback delivery may lag or overlap and should not
	// be treated as a total order. Panics in this callback propagate to the caller.
	// Nil means no notification.
	OnStateChange func(Transition)

	// Clock returns the current time. Nil defaults to time.Now.
	// Must be non-blocking, must not panic, and must not call Breaker methods
	// (deadlock risk). Useful for deterministic testing.
	Clock func() time.Time
}

BreakerConfig configures a circuit breaker.

type BreakerState added in v0.42.0

type BreakerState int

BreakerState represents the current state of a circuit breaker.

const (
	StateClosed BreakerState = iota
	StateOpen
	StateHalfOpen
)

func (BreakerState) String added in v0.42.0

func (s BreakerState) String() string

type DebounceOption

type DebounceOption func(*debounceConfig)

DebounceOption configures a Debouncer.

func MaxWait

func MaxWait(d time.Duration) DebounceOption

MaxWait caps the maximum delay under continuous activity. When continuous calls keep resetting the trailing timer, MaxWait guarantees execution after this duration from the first call in a burst. Zero (default) means no cap — trailing edge only, which can defer indefinitely under continuous activity. Panics if d < 0.

type Debouncer

type Debouncer[T any] struct {
	// contains filtered or unexported fields
}

Debouncer coalesces rapid calls, executing fn with the latest value after a quiet period of at least wait. At most one fn execution runs at a time; calls during execution queue the latest value for a fresh timer cycle after completion.

A single owner goroutine manages all state — no mutex contention, no stale timer callbacks. Call, Cancel, and Flush communicate via channels; the owner processes events sequentially.

Value capture: Call stores the latest T by value. No deep copy is performed. If T contains pointers, slices, or maps, the caller must not mutate their contents after Call.

Panic behavior: fn runs in a spawned goroutine. If fn panics, the owner goroutine's state is preserved via deferred completion signaling, and the panic propagates normally (typically crashing the process).

Reentrancy: Call and Cancel are safe to invoke from within fn on the same Debouncer. Flush and Close from within fn will deadlock — fn completion must signal before either can proceed.

Close must be called when the Debouncer is no longer needed to stop the owner goroutine. Use-after-Close panics. Close is idempotent. Operations concurrent with Close may block until Close completes, then panic.

func NewDebouncer

func NewDebouncer[T any](wait time.Duration, fn func(T), opts ...DebounceOption) *Debouncer[T]

NewDebouncer creates a trailing-edge debouncer that executes fn with the latest value after wait elapses with no new calls. Panics if wait <= 0 or fn is nil.

func (*Debouncer[T]) Call

func (d *Debouncer[T]) Call(v T)

Call schedules fn with v. If a previous call is pending, its value is replaced with v and the trailing timer resets. If fn is currently executing, v is queued for a fresh timer cycle after completion.

func (*Debouncer[T]) Cancel

func (d *Debouncer[T]) Cancel() bool

Cancel stops any pending execution. Returns true if pending work was canceled, false if there was nothing pending. If a Flush is blocked waiting for pending work, Cancel unblocks it and the Flush returns false.

func (*Debouncer[T]) Close

func (d *Debouncer[T]) Close()

Close stops the owner goroutine. Any pending work is discarded. If fn is currently executing, Close waits for it to complete. If a Flush triggered the currently running execution, Flush returns true (the execution completes). If a Flush is waiting for pending work that Close discards, Flush returns false. Close is idempotent — subsequent calls return immediately. After Close, Call, Cancel, and Flush will panic. Operations concurrent with Close may block until Close completes.

Close must not be called from within fn on the same Debouncer — this will deadlock because fn completion must signal before Close can proceed.

func (*Debouncer[T]) Flush

func (d *Debouncer[T]) Flush() bool

Flush executes pending work immediately. Returns true if fn was executed as a result of this call, false if there was nothing pending.

When fn is already running with pending work queued, Flush blocks until the current fn completes and the pending work executes. New Calls that arrive during a flushed execution do not extend the Flush — they are scheduled normally via timer after Flush returns.

Only one Flush waiter is supported at a time. If a Flush is already waiting, subsequent Flush calls return false immediately.

Flush must not be called from within fn on the same Debouncer — this will deadlock because fn completion must signal before Flush can proceed.

type Snapshot added in v0.42.0

type Snapshot struct {
	State               BreakerState
	Successes           int
	Failures            int
	ConsecutiveFailures int
	Rejected            int
	OpenedAt            time.Time
}

Snapshot is a point-in-time view of breaker state and metrics. Successes and Failures reset when the breaker transitions to closed. ConsecutiveFailures resets on any success (including while closed). Rejected is a lifetime counter. OpenedAt is the zero time when State is StateClosed.

type Transition added in v0.42.0

type Transition struct {
	From BreakerState
	To   BreakerState
	At   time.Time
}

Transition describes a circuit breaker state change.

Jump to

Keyboard shortcuts

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