Skip to content

refactor: use monotonic clock for state timing#25

Merged
costela merged 3 commits into
mainfrom
feat/monotonic-clock
Jul 9, 2026
Merged

refactor: use monotonic clock for state timing#25
costela merged 3 commits into
mainfrom
feat/monotonic-clock

Conversation

@costela

@costela costela commented Jun 26, 2026

Copy link
Copy Markdown
Member

What

State timing (Circuit.openedAt and SlidingWindowBreaker.currentStart) now uses the monotonic clock instead of wall-clock micros.

Why

Both previously stored time.Now().UnixMicro() and measured elapsed time with time.Since(time.UnixMicro(...)), which strips the monotonic reading and falls back to wall-clock subtraction. A wall-clock jump (NTP step, manual change) could pin a circuit open past its halfOpenDelay, or skip the open period entirely — exactly the conditions under which a resilience library is most likely to be running.

How

  • A package-level monotonic reference start = time.Now() captured once at load.
  • nowNanos() / sinceNanos() helpers store and compare elapsed nanoseconds against it.
  • Keeps the lock-free single-int64 atomic state; transitions are now immune to wall-clock jumps.
  • openedAt/currentStart are now monotonic-since-start nanoseconds (unexported state, not wall-clock meaningful).

Notes

  • Pure behavior-preserving refactor under normal clocks; no API change.
  • The pre-existing EWMA test flakiness (TestBreaker_Observe_State/...EWMA flaky) is unrelated and addressed in a separate PR.

🤖 Generated with Claude Code

State timing (Circuit.openedAt and SlidingWindowBreaker.currentStart)
previously stored time.Now().UnixMicro() and measured elapsed time with
time.Since(time.UnixMicro(...)), which strips the monotonic clock reading
and falls back to wall-clock subtraction. Wall-clock jumps (NTP steps,
manual clock changes) could therefore pin a circuit open past its delay or
skip the open period entirely - precisely the conditions under which a
resilience library is most likely to run.

Capture a package-level monotonic reference (start) once and store/compare
elapsed nanoseconds against it via nowNanos/sinceNanos. This keeps the
lock-free single-int64 atomic state while making transitions immune to
wall-clock jumps. openedAt/currentStart are now monotonic-since-start
nanoseconds and no longer wall-clock meaningful (they are unexported state).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@costela costela marked this pull request as ready for review June 26, 2026 18:56
@costela costela requested a review from Copilot June 26, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors internal state timing for Circuit.openedAt and SlidingWindowBreaker.currentStart to use a monotonic time source (elapsed nanoseconds since a package-load reference) so state transitions aren’t affected by wall-clock jumps.

Changes:

  • Replace wall-clock microsecond timestamps with monotonic “nanos since start” timestamps for circuit state timing.
  • Add start, nowNanos(), and sinceNanos() helpers to store/compare monotonic elapsed time.
  • Update tests to simulate half-open timing using the new monotonic timestamp scheme.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
hoglet.go Introduces monotonic time helpers and migrates circuit open/half-open timing to monotonic nanos.
breaker.go Migrates sliding-window timing from wall-clock micros to monotonic nanos.
hoglet_test.go Updates test setup to simulate half-open passage of time using monotonic timestamps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hoglet.go Outdated
Comment thread hoglet_test.go
Comment thread breaker.go
costela and others added 2 commits July 9, 2026 17:12
Backdate the monotonic reference point by one nanosecond so nowNanos is
always > 0 even on platforms with coarse timer resolution, where
open()/reopen() could otherwise store 0 and leave the circuit closed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unconditional CompareAndSwap updated currentStart on every
observation, making it track the last call instead of the window start.
Under steady traffic the windows never rotated, so old failures never
decayed and the breaker degraded to a lifetime failure rate.

Pre-existing bug on main, surfaced by review on the monotonic clock
change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@costela costela requested a review from a team July 9, 2026 15:24
@costela costela merged commit 2462cd1 into main Jul 9, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants