Documentation
¶
Overview ¶
Package clock provides a minimal, injectable wall-clock abstraction so that time-dependent code paths — the checkpoint cadence (store/checkpoint) and the Bolt session/connection deadlines (bolt/server) — can be driven by a deterministic fake clock under test (notably the deterministic simulation testing harness in internal/sim) instead of reading real wall time.
The interface is deliberately the smallest subset the injection sites need: Now, Since, Until, After, NewTimer, and NewTicker. The production default, returned by Real, delegates to the standard library and is byte-for-byte equivalent to calling the time package directly. Call sites that take a Clock therefore behave identically to the pre-injection code on the default path; only a test that supplies an alternative implementation observes any difference.
Why an interface and not the time package ¶
The GoGraph reliability mandate requires deterministic crash/recovery simulation. Wall-clock reads make a run non-reproducible. Routing the two time-sensitive control loops through a Clock lets a simulator advance time in fixed logical steps, so a given seed replays identically. The WAL group-commit path is intentionally NOT routed through this abstraction: it is pure sync.Cond leader/follower fsync coalescing with no time reads, so it is already time-deterministic and needs no injection.
Concurrency contract ¶
Real returns a stateless value that is safe for concurrent use by any number of goroutines. Implementations supplied by tests must document their own contract; the standard fake used by the simulation harness is driven from a single goroutine.
Index ¶
- type Clock
- type Fake
- func (f *Fake) Advance(d time.Duration)
- func (f *Fake) After(d time.Duration) <-chan time.Time
- func (f *Fake) NewTicker(d time.Duration) Ticker
- func (f *Fake) NewTimer(d time.Duration) Timer
- func (f *Fake) Now() time.Time
- func (f *Fake) Set(t time.Time)
- func (f *Fake) Since(t time.Time) time.Duration
- func (f *Fake) Until(t time.Time) time.Duration
- type Ticker
- type Timer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Clock ¶
type Clock interface {
// Now reports the clock's current instant, mirroring [time.Now].
Now() time.Time
// Since reports the duration elapsed since t, mirroring [time.Since].
// It is equivalent to Now().Sub(t).
Since(t time.Time) time.Duration
// Until reports the duration until t, mirroring [time.Until]. It is
// equivalent to t.Sub(Now()).
Until(t time.Time) time.Duration
// After returns a channel that delivers the clock's current time after
// at least d has elapsed, mirroring [time.After]. Unlike [NewTimer] the
// underlying timer cannot be stopped, so prefer NewTimer when the wait
// may be abandoned.
After(d time.Duration) <-chan time.Time
// NewTimer creates a [Timer] that fires once after at least d, mirroring
// [time.NewTimer].
NewTimer(d time.Duration) Timer
// NewTicker creates a [Ticker] that fires repeatedly every d, mirroring
// [time.NewTicker]. A non-positive d panics, matching [time.NewTicker].
NewTicker(d time.Duration) Ticker
}
Clock is an injectable source of wall-clock time. The production implementation (Real) delegates to the standard library; tests may supply a deterministic fake.
Every method mirrors the semantics of its time package counterpart so that substituting Real for direct time calls is behaviour-preserving.
type Fake ¶
type Fake struct {
// contains filtered or unexported fields
}
Fake is a deterministic Clock whose time advances only when Fake.Advance or Fake.Set is called. It never reads wall time, so code driven by a Fake replays identically for a given sequence of advances. Timers and tickers created from a Fake fire synchronously during the Advance that crosses their deadline, with their delivery channels buffered so a fire is never dropped even if no goroutine is waiting.
Fake is intended for tests and for the deterministic simulation harness.
Concurrency contract ¶
Fake is safe for concurrent use: every method takes an internal mutex. The channel sends performed during Advance are non-blocking (channels are buffered with capacity 1 and a full channel is left as-is, matching time.Ticker's drop-on-backlog behaviour), so Advance never blocks on a slow consumer.
func (*Fake) Advance ¶
Advance moves the Fake's clock forward by d, firing every timer and ticker whose deadline falls within the elapsed interval. Waiters are fired in deadline order so the sequence is deterministic. A negative d is treated as zero.
func (*Fake) After ¶
After returns a channel that fires once the Fake advances at least d past the current time.
func (*Fake) NewTicker ¶
NewTicker registers a ticker that fires every d as the Fake advances. A non-positive d panics, matching time.NewTicker.
func (*Fake) NewTimer ¶
NewTimer registers a one-shot timer that fires when the Fake advances at least d past the current time.
func (*Fake) Set ¶
Set moves the Fake's clock to t (which must not be before the current time), firing every waiter whose deadline falls in the interval. A target before the current time is ignored.
type Ticker ¶
type Ticker interface {
// C returns the ticker's delivery channel, the analogue of the exported
// C field on [time.Ticker].
C() <-chan time.Time
// Stop halts the ticker, mirroring [time.Ticker.Stop]. It does not close
// the channel.
Stop()
}
Ticker is the injectable analogue of time.Ticker. Its channel delivers a tick on each period; Stop halts further ticks.
type Timer ¶
type Timer interface {
// C returns the timer's delivery channel, the analogue of the exported
// C field on [time.Timer].
C() <-chan time.Time
// Stop prevents the timer from firing, mirroring [time.Timer.Stop]. It
// returns true if it stopped the timer before it fired.
Stop() bool
}
Timer is the injectable analogue of time.Timer. Its channel delivers one tick when the timer fires; Stop prevents a not-yet-fired timer from firing.