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
- type Clock
- type Detector
- type Fake
- func (f *Fake) Advance(d time.Duration)
- func (f *Fake) JumpWall(d time.Duration)
- func (f *Fake) Mark() Mono
- func (f *Fake) NewTicker(d time.Duration) *time.Ticker
- func (f *Fake) NewTimer(d time.Duration) *time.Timer
- func (f *Fake) Now() time.Time
- func (f *Fake) Set(t time.Time)
- func (f *Fake) Since(m Mono) time.Duration
- type Jump
- type Mono
Constants ¶
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.
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 ¶
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 ¶
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.
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 ¶
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 ¶
Advance moves wall time and monotonic time by the same amount: ordinary time passing.
func (*Fake) JumpWall ¶
JumpWall moves wall time only, forwards or backwards. This is an NTP correction: durations taken from Mark and Since must not notice it.
type Jump ¶
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.