refactor: use monotonic clock for state timing#25
Merged
Conversation
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>
Contributor
There was a problem hiding this comment.
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(), andsinceNanos()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.
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>
totallyunknown
approved these changes
Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
State timing (
Circuit.openedAtandSlidingWindowBreaker.currentStart) now uses the monotonic clock instead of wall-clock micros.Why
Both previously stored
time.Now().UnixMicro()and measured elapsed time withtime.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 itshalfOpenDelay, or skip the open period entirely — exactly the conditions under which a resilience library is most likely to be running.How
start = time.Now()captured once at load.nowNanos()/sinceNanos()helpers store and compare elapsed nanoseconds against it.int64atomic state; transitions are now immune to wall-clock jumps.openedAt/currentStartare now monotonic-since-start nanoseconds (unexported state, not wall-clock meaningful).Notes
TestBreaker_Observe_State/...EWMA flaky) is unrelated and addressed in a separate PR.🤖 Generated with Claude Code