clock

package
v0.0.0-...-b8a15ae Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package clock defines the Clock interface and its fake, so time is injectable in tests.

Wall time and monotonic time are different things

Now is wall time: logical time, the kind that is stored and shown. Mark and Since are monotonic: duration, the kind that decides whether a lease has expired, a timeout has elapsed or a backoff is over. Wall time can move sideways when NTP corrects the machine; monotonic time cannot. Measuring a duration with wall time is how an NTP correction turns into a lease that looks expired while its owner is still working.

Mono is a distinct type so that mistake is a compile error rather than a review comment. A deadline held as a Mono cannot be written to a column or sent to another process, which is the rule this project needs: lease time is never compared across processes. Every Store method that involves a lease takes its time from the database or from the process itself, never from a now value handed in by a caller.

Choosing between synctest and Fake

Both exist. They answer different questions, and the choice is not open:

Kind of test                                     Tool
-------------------------------------------------------------------------
Loop, timeout, retry or backoff logic, no DB     testing/synctest
Timers and tickers firing in order               testing/synctest
Anything that reaches internal/store             clock.Fake and real SQLite
A wall clock jump, an NTP correction, DST        clock.Fake

The reason for the split is that a synctest bubble only advances its clock when every goroutine in it is durably blocked, and a goroutine blocked on SQLite file I/O is not durably blocked. The paths that touch disk, which is most of this scheduler, cannot rely on the bubble. They take a Fake and step it themselves.

The reason Fake does not fake timers is that same split seen from the other side. A time.Timer is driven by the runtime through a channel this package cannot fill, so a hand-built fake timer would be a poor copy of what the bubble already does properly. Fake.NewTimer and Fake.NewTicker return real timers, which are virtual inside a bubble and real outside it. Test timer behaviour in a bubble; test wall clock behaviour with a Fake.

Index

Constants

View Source
const DefaultJumpThreshold = 5 * time.Second

DefaultJumpThreshold is the deviation between wall time and monotonic time that counts as a clock jump rather than as scheduling noise.

Variables

This section is empty.

Functions

This section is empty.

Types

type Clock

type Clock interface {
	// Now is wall time in UTC, with no monotonic reading attached.
	Now() time.Time
	// Mark takes a monotonic reference point.
	Mark() Mono
	// Since is the monotonic duration elapsed since a mark. It is unaffected by
	// wall clock changes.
	Since(Mono) time.Duration
	// NewTimer and NewTicker are the package time constructors. Inside a
	// testing/synctest bubble they run on the bubble's fake clock; see doc.go
	// for when to use synctest and when to use Fake.
	NewTimer(time.Duration) *time.Timer
	NewTicker(time.Duration) *time.Ticker
}

Clock is the whole time surface domain code may use. Wall time is logical time: scheduled_for, created_at, anything a human or another process reads. Monotonic time is duration: lease deadlines, timeouts, backoff. Mixing them is how an NTP correction becomes a false lease expiry.

func System

func System() Clock

System returns the clock every production entry point passes down.

type Detector

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

Detector compares wall time against monotonic time and reports the difference. It reports only. What to do about a jump, pausing a reaper on a backwards jump or letting catch-up absorb a forwards one, is scheduler policy and lives with the scheduler.

func NewDetector

func NewDetector(c Clock, threshold time.Duration) *Detector

NewDetector takes the first sample immediately, so the first Check measures against construction time. A threshold of zero or less means DefaultJumpThreshold.

func (*Detector) Check

func (d *Detector) Check() (Jump, bool)

Check takes a sample and reports a jump when wall time and monotonic time disagree by more than the threshold since the previous sample. The sample becomes the new baseline either way, so one jump is reported once.

func (*Detector) Run

func (d *Detector) Run(ctx context.Context, every time.Duration, out chan<- Jump) error

Run samples every interval until ctx is cancelled, sending each jump to out. A receiver that is slow to read delays the next sample; jumps are rare enough that dropping them silently would be the worse trade.

type Fake

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

Fake is a deterministic Clock for tests. Wall time and monotonic time move separately, which is the point: it can simulate an NTP correction that moves the wall clock while leaving durations alone.

Fake does not fake timers. NewTimer and NewTicker return real ones, which a testing/synctest bubble virtualises anyway. See doc.go.

func NewFake

func NewFake(t0 time.Time) *Fake

NewFake returns a Fake whose wall clock reads t0. The monotonic clock starts at zero, so the zero Mono is the moment the Fake was created.

func (*Fake) Advance

func (f *Fake) Advance(d time.Duration)

Advance moves wall time and monotonic time by the same amount: ordinary time passing.

func (*Fake) JumpWall

func (f *Fake) JumpWall(d time.Duration)

JumpWall moves wall time only, forwards or backwards. This is an NTP correction: durations taken from Mark and Since must not notice it.

func (*Fake) Mark

func (f *Fake) Mark() Mono

func (*Fake) NewTicker

func (f *Fake) NewTicker(d time.Duration) *time.Ticker

func (*Fake) NewTimer

func (f *Fake) NewTimer(d time.Duration) *time.Timer

func (*Fake) Now

func (f *Fake) Now() time.Time

func (*Fake) Set

func (f *Fake) Set(t time.Time)

Set puts wall time at an absolute instant, leaving monotonic time alone. It is JumpWall for tests that find the destination easier to write than the delta.

func (*Fake) Since

func (f *Fake) Since(m Mono) time.Duration

type Jump

type Jump struct {
	At    time.Time
	Delta time.Duration
}

Jump is one observed wall clock correction. Delta is positive when the wall clock moved forwards relative to elapsed monotonic time and negative when it moved backwards. At is the wall clock reading after the jump.

type Mono

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

Mono is a monotonic reference point. It is a distinct type, not a time.Time, so the compiler refuses the mistake the project cannot afford: a monotonic reading stored in the database or sent to another process. Its zero value is the moment the clock was created.

What the type prevents: a Mono cannot be assigned to a time.Time field, cannot be passed to a database/sql query (the driver rejects an unknown struct type), and carries no data through encoding/json because it has no exported fields and no marshaller.

What it does not prevent: fmt verbs still print it, unsafe and reflection still reach the field, and json.Marshal produces the empty object "{}" rather than an error. The guarantee is that no Mono value can silently become a timestamp somewhere else, not that the value is unreachable.

Jump to

Keyboard shortcuts

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