Repository navigation
fix: let the limiter count only calls the circuit lets through - #38
Merged
Merged
Conversation
Wrap claimed the circuit's state for a call, including the half-open
call, before any middleware ran, and the limiter took a slot before the
circuit checked the call. As a result:
- calls into an open circuit waited for or took a slot instead of
failing fast;
- a call the limiter rejected used up the half-open call, keeping the
circuit open for another full delay;
- a call that waited for a slot entered the circuit with the state it
had seen before waiting, even if the circuit had opened meanwhile.
Wrap now only reads the state for the middleware. The circuit decides
on its current state, and claims the half-open call, once the call
reaches it, and the limiter lets calls into an open circuit through
without a slot. For custom middleware, a half-open state no longer means
the call gets the half-open call.
The extra state read cost ~1.5ns per call until the closed case of
Circuit.State was made inlinable. benchstat, n=10, main and this branch
run interleaved, 1s each:
./main.test|./pr.test -test.run '^$' -test.bench BenchmarkHoglet_Do_ \
-test.benchmem -test.benchtime 1s -test.cpu 1,8,24 -test.count 1
Hoglet_Do_EWMA 27.0n 27.3n ~ (p=0.325)
Hoglet_Do_EWMA-8 3.41n 3.41n ~ (p=0.493)
Hoglet_Do_EWMA-24 1.97n 1.99n +1.07% (p=0.033)
Hoglet_Do_SlidingWindow 103.4n 104.3n +0.92% (p=0.010)
Hoglet_Do_SlidingWindow-8 53.0n 52.0n -1.88% (p=0.011)
Hoglet_Do_SlidingWindow_rotating 103.5n 104.8n +1.16% (p=0.002)
Hoglet_Do_SlidingWindow_rotating-8 54.9n 54.8n ~ (p=0.796)
Hoglet_Do_SlidingWindow_rotating-24 43.8n 43.8n ~ (p=0.424)
Hoglet_Do_SlidingWindow-24 is bimodal on main (±53%) and left out.
allocs/op stay 0. An earlier control with identical code in both
binaries shifted by up to 1.89%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The implementation matches the stated behavior and includes focused regression coverage for all identified failure modes.
0 open findings
What changed in this PR
Moves circuit admission checks behind middleware delays while ensuring open-circuit calls bypass concurrency-limit slots.
Changes:
- Defers current-state and half-open admission decisions to the circuit.
- Makes concurrency limiters skip open-circuit calls.
- Adds regression coverage for slot usage and state changes while waiting.
| File | Description |
|---|---|
hoglet.go |
Updates circuit state handling and middleware contract. |
hoglet_test.go |
Tests half-open rejection and delayed-call behavior. |
limiter.go |
Bypasses permits for open-circuit calls. |
limiter_test.go |
Tests permit release and open-circuit bypass. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
costela
marked this pull request as ready for review
October 9, 2026 10:48
vietuoso
approved these changes
Oct 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.
Why
Wrapclaimed the circuit's state for a call, including the half-open call, before any middleware ran, and the concurrency limiter took a slot before the circuit checked the call. As a result:What
Wraponly reads the state for the middleware. The circuit decides on its current state, and claims the half-open call, once the call reaches it.Circuit.Stateis split off to be inlinable, which removes the ~1.5ns the extra state read cost per call. Remaining difference vsmain: within ±1.2% (benchstat, n=10, interleaved runs; command and table in the commit message).main). The permit-leak test now uses a call that loses the half-open race, since calls into an open circuit no longer take a slot.Behavior change for custom middleware: a half-open state passed to
ObserverForCallno longer means the call gets the half-open call.Independent of #37; the tests pass with and without it.
🤖 Generated with Claude Code