manual

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 7 Imported by: 3

Documentation

Overview

Package manual provides fixed timestamps and explicitly advanced test clocks. NewFixed is an immutable wall-time seam; New constructs a concurrency-safe Clock whose elapsed progress is independent of Jump's wall-time corrections. The package does not replace process time or automatically advance blocked goroutines, and it requires no cgo or external service.

Clock owns bounded schedules and advancement waiters. Call Close to release scheduled work. Advancements may start callback goroutines and wait for them; callbacks must cooperate and use a nested advancement waiter to permit future progress. Closing the clock does not wait for running callbacks.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrActiveLimit reports exhaustion of the scheduled-object or waiter budget.
	ErrActiveLimit = errors.New("manual clock: active object limit exceeded")
	// ErrWorkLimit reports exhaustion of a coordinator run's shared event budget.
	ErrWorkLimit = errors.New("manual clock: advancement work limit exceeded")
	// ErrClosed reports an operation attempted after the clock is closed.
	ErrClosed = errors.New("manual clock: closed")
	// ErrBackwardAdvance reports an AdvanceTo target before the current wall time.
	ErrBackwardAdvance = errors.New("manual clock: backward advance")
	// ErrInvalidLimits reports a zero or negative resource budget.
	ErrInvalidLimits = errors.New("manual clock: invalid limits")
)

Functions

This section is empty.

Types

type Callback

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

Callback is a concurrency-safe scheduled function created by Clock.AfterFunc. Its zero value is not usable. Clock advancement owns started invocations; stopping or closing the clock cannot interrupt a function already running.

func (*Callback) Reset

func (callback *Callback) Reset(duration time.Duration) (bool, error)

Reset reschedules the callback and reports prior scheduling activity, not whether an invocation is running. Non-positive durations are immediately due. A running invocation is not awaited; explicit reentrant/concurrent progress may start an overlapping invocation. ErrClosed, ErrActiveLimit when reactivating, and clock.ErrOverflow preserve the prior schedule on rejection.

func (*Callback) Stop

func (callback *Callback) Stop() bool

Stop prevents an active callback from starting and releases its schedule. It returns false for an already-started or stopped invocation and does not wait.

type Clock

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

Clock is a concurrency-safe deterministic clock. Time changes only through Advance, AdvanceTo, or Jump. Construct it with New; the zero value is not a usable clock. Do not copy a Clock after use.

State and resource Stop/Reset methods share one mutex. Reading time, creating resources, advancing, jumping and closing may run concurrently. Construction starts no goroutine; an advancement coordinator starts owned callback goroutines outside the mutex. Callbacks must eventually return; Close cannot terminate them. The caller owns Close and callback cooperation.

func New

func New(start time.Time, options ...Option) (*Clock, error)

New constructs a clock at an explicit wall timestamp, including time.Time{}. It preserves the location and strips any process-local monotonic reading so Jump can model wall movement independently from elapsed progress. Nil options are ignored; an invalid option returns nil and its error. Default limits are 65,536 scheduled objects, 65,536 waiters, and 1,000,000 fired events per coordinator run. The caller must Close the returned clock.

func (*Clock) Advance

func (clock *Clock) Advance(duration time.Duration) (*Waiter, error)

Advance requests non-negative elapsed progress from the current instant. The first request runs the coordinator synchronously; nested or concurrent requests return a Waiter without starting another coordinator. Events become due in deadline and registration order, with callbacks outside internal locks.

Running callbacks freeze implicit future progress. Same-instant work may run; a callback waiting for future work must submit a nested Advance and call Wait to allow progress through its target. Arbitrary callbacks that never return can block the coordinating Advance; Advance itself has no cancellation input.

Invalid duration, closure, waiter limits and target overflow return no waiter. Work-limit failure may return a non-nil waiter and ErrWorkLimit; Wait then yields a partial Result. Already-started callbacks are drained on that failure. Use the waiter to synchronize accepted work, including reentrant requests.

func (*Clock) AdvanceTo

func (clock *Clock) AdvanceTo(target time.Time) (*Waiter, error)

AdvanceTo moves forward until target wall time. Backward wall movement must use Jump so it cannot accidentally reverse monotonic progress. Targets farther away than time.Duration can represent return clock.ErrOverflow. Target conversion and request registration are separate operations; a concurrent Advance or Jump can change the wall time between them.

func (*Clock) AfterFunc

func (clock *Clock) AfterFunc(duration time.Duration, function func()) (clockpkg.Callback, error)

AfterFunc schedules an owned callback. A panic is recovered and counted in the advancement Result; panic payloads are never retained. A nil function returns nil and clock.ErrInvalidCallback. Non-positive durations become due at the current instant, but no callback starts until advancement. Scheduling can return ErrClosed, ErrActiveLimit or clock.ErrOverflow. Functions may reenter the clock, but must cooperate with advancement and eventually return.

func (*Clock) Close added in v1.1.0

func (clock *Clock) Close() error

Close immediately and idempotently releases every active object owned by the clock. It wakes active sleepers and fails pending advancement waiters with ErrClosed, stops future scheduled events and starts no goroutine. It always returns nil, including repeated calls. Existing timer/ticker channels remain open and any buffered value remains readable. Read-only time methods remain available. Running callbacks are neither stopped nor awaited; an already-running Advance can still be blocked by them after Close returns.

func (*Clock) Jump

func (clock *Clock) Jump(delta time.Duration) error

Jump changes wall time without changing monotonic progress or event deadlines. Negative deltas are valid. ErrClosed and clock.ErrOverflow leave the wall offset unchanged; overflow is checked against the accumulated jump duration.

func (*Clock) Mark

func (clock *Clock) Mark() time.Duration

Mark captures the current monotonic progress as a time.Duration token.

func (*Clock) Measure

func (clock *Clock) Measure() func() time.Duration

Measure captures current monotonic progress and returns a concurrency-safe closure whose result is unaffected by Jump.

func (*Clock) NewTicker

func (clock *Clock) NewTicker(duration time.Duration) (clockpkg.Ticker, error)

NewTicker creates an owned ticker. Its one-element channel drops ticks while the receiver is backpressured, matching the standard library policy. A non-positive period returns nil and clock.ErrInvalidDuration. Scheduling can also return ErrClosed, ErrActiveLimit or clock.ErrOverflow. The owner must Stop the ticker; failure to compute its next deadline automatically stops it.

func (*Clock) NewTimer

func (clock *Clock) NewTimer(duration time.Duration) (clockpkg.Timer, error)

NewTimer creates an owned one-shot timer with a one-value buffered channel. Non-positive durations are due at the current instant but require Advance to fire. Failure returns nil with ErrClosed, ErrActiveLimit or clock.ErrOverflow; stopping an unused timer releases its schedule.

func (*Clock) Now

func (clock *Clock) Now() time.Time

Now returns the current wall time. The starting location is preserved.

func (*Clock) Shutdown deprecated

func (clock *Clock) Shutdown() error

Shutdown delegates to Close.

Deprecated: use Close.

func (*Clock) Since

func (clock *Clock) Since(start time.Time) time.Duration

Since returns wall-clock subtraction against Now. Use Mark and SinceMark for elapsed measurement that must remain correct across Jump. The result may be negative after a backward jump or when start is in the future.

func (*Clock) SinceMark

func (clock *Clock) SinceMark(mark time.Duration) time.Duration

SinceMark returns monotonic progress since mark, independent of wall jumps. Use a Mark from this clock; arbitrary tokens are not validated.

func (*Clock) Sleep

func (clock *Clock) Sleep(ctx context.Context, duration time.Duration) error

Sleep blocks until manual advancement reaches the deadline or ctx is done. A nil context panics on an open clock. Closed clocks return ErrClosed before checking context or duration. Otherwise a canceled context wins before registration, and non-positive durations succeed without scheduling work. Scheduling can fail with ErrClosed, ErrActiveLimit or clock.ErrOverflow. Close wakes registered sleepers with ErrClosed; cancellation releases their schedule. If completion, closure and cancellation race, either outcome may win.

func (*Clock) Snapshot

func (clock *Clock) Snapshot() Snapshot

Snapshot returns bounded diagnostic counters without callback payloads.

type Fixed

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

Fixed is an immutable, concurrency-safe wall clock. Its zero value returns time.Time{}; it has no scheduled resources or release obligation.

func NewFixed

func NewFixed(now time.Time) Fixed

NewFixed returns an immutable clock fixed at now, preserving its location and any process-local monotonic reading. It accepts time.Time{}.

func (Fixed) Measure

func (Fixed) Measure() func() time.Duration

Measure returns an elapsed measurement that remains zero for a fixed clock.

func (Fixed) Now

func (fixed Fixed) Now() time.Time

Now returns the fixed timestamp without changing its location.

func (Fixed) Since

func (fixed Fixed) Since(start time.Time) time.Duration

Since returns the duration from start to the fixed timestamp. If both values contain compatible monotonic readings, time.Time.Sub uses them.

type Limits

type Limits struct {
	// MaxActive independently caps scheduled objects and outstanding waiters.
	// Running callbacks have already left the scheduled-object count.
	MaxActive int
	// MaxWorkPerAdvance caps fired events in one coordinator run, including
	// concurrent/nested requests and ticker ticks dropped by backpressure.
	MaxWorkPerAdvance int
}

Limits bounds scheduled objects, outstanding advancement waiters, and work processed by a Clock. MaxActive applies independently to scheduled objects and advancement waiters.

type Option

type Option func(*config) error

Option configures a Clock during construction.

func WithLimits

func WithLimits(limits Limits) Option

WithLimits replaces the default active-object and advancement budgets. Both fields must be positive or New returns nil and ErrInvalidLimits.

type Result

type Result struct {
	// StartedAt is elapsed progress when the request was registered.
	StartedAt time.Duration
	// EndedAt is the requested target on success or progress at failure.
	EndedAt time.Duration
	// Triggered counts fired events, including undelivered ticker ticks.
	Triggered int
	// Callbacks counts callback invocations started after submission.
	Callbacks int
	// Panics counts recovered callback panics recorded after submission.
	Panics int
}

Result summarizes work relative to an advancement request's submission. Concurrent/nested requests share a coordinator, so their counts can overlap. A failed request reports partial work; its counts are captured when it fails, not updated for callbacks that finish later during draining.

type Snapshot

type Snapshot struct {
	// Now is the wall timestamp, including the accumulated jump offset.
	Now time.Time
	// Elapsed is manually advanced progress, unaffected by wall jumps.
	Elapsed time.Duration
	// Active counts scheduled objects, excluding running callbacks and waiters.
	Active int
	// Closed reports whether Close has released scheduled work.
	Closed bool
}

Snapshot is a bounded view of manual-clock state.

type Ticker

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

Ticker is a concurrency-safe manually advanced periodic timer created by Clock.NewTicker. Its zero value is not usable; the owner must Stop it.

func (*Ticker) C

func (ticker *Ticker) C() <-chan time.Time

C returns the ticker's one-value buffered channel; undrained later ticks are dropped. Stop and Clock.Close never close or drain it.

func (*Ticker) Reset

func (ticker *Ticker) Reset(duration time.Duration) error

Reset changes the period and schedules the next tick from current elapsed progress without draining an old buffered tick. Non-positive periods return clock.ErrInvalidDuration. ErrClosed, ErrActiveLimit when reactivating, and clock.ErrOverflow also reject the reset without changing the prior schedule.

func (*Ticker) Stop

func (ticker *Ticker) Stop()

Stop prevents future ticks and releases its schedule. It is idempotent and does not remove a timestamp already buffered in C.

type Timer

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

Timer is a concurrency-safe manually advanced one-shot timer created by Clock.NewTimer. Its zero value is not usable; the clock owns its schedule.

func (*Timer) C

func (timer *Timer) C() <-chan time.Time

C returns the timer's one-value buffered channel. It is never closed by Stop or Clock.Close. A delivered timestamp includes the current wall offset.

func (*Timer) Reset

func (timer *Timer) Reset(duration time.Duration) (bool, error)

Reset reschedules the timer and reports whether it was active beforehand. Non-positive durations become due at the current instant. A successful reset drains any old buffered timestamp; rejected resets preserve schedule/value. Errors are ErrClosed, ErrActiveLimit when reactivating, or clock.ErrOverflow.

func (*Timer) Stop

func (timer *Timer) Stop() bool

Stop prevents an active timer from firing and releases its schedule. It returns false after firing or a prior stop and does not drain a buffered value.

type Waiter

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

Waiter synchronizes completion of work attributed to an advancement request. Obtain it from Advance or AdvanceTo; its zero value is not usable. Wait may be called concurrently or repeatedly. Context cancellation cancels only that Wait call, not the underlying request or callbacks.

func (*Waiter) Wait

func (waiter *Waiter) Wait(ctx context.Context) (Result, error)

Wait returns the completed result, including partial work with ErrWorkLimit or ErrClosed, or a zero Result with ctx.Err() if the pending wait is canceled. An already-completed waiter returns its result even with a canceled context. For a pending waiter, ctx must be non-nil. Waiting explicitly permits future progress through this request's target while callbacks are running.

Jump to

Keyboard shortcuts

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