Nobody has started this yet — be first.
Business impact
The circuit breaker exists so a dead upstream does not tie up gateway capacity waiting out repeated timeouts. A route configured with failure_threshold: 5 is supposed to trip after the 5th consecutive failure. Right now it takes a 6th -- meaning every route's breaker is quietly one failure more tolerant than whoever configured it intended. For a route tuned with a low threshold specifically to fail fast (failure_threshold: 1 or 2), that off-by-one is proportionally huge: a threshold: 1 route effectively behaves like threshold: 2, doubling how many failed requests get sent into an already-failing upstream before the breaker does its job.
Problem
Breaker.on_failure() trips the breaker with if self._consecutive_failures > self._failure_threshold. With failure_threshold=3, three consecutive failures leave _consecutive_failures == 3, and 3 > 3 is False -- the breaker stays closed. It takes a fourth failure to trip.
Current behavior
A route configured with circuit_breaker: {failure_threshold: 3} sends its 4th request to a failing upstream too, instead of short-circuiting it -- on_failure()'s > comparison against _failure_threshold is one failure too tolerant.
Expected behavior
With failure_threshold=3, the breaker trips on exactly the 3rd consecutive failure -- the request that pushes _consecutive_failures to 3 should be the last one that reaches the upstream before the next one gets short-circuited.
Steps to reproduce
Why this matters
> vs >= on a counter comparison is one of the easiest single-character mistakes to make and one of the easiest to miss in review, because both versions "work" in the sense that the breaker does eventually trip -- just one failure later than configured, every time, for every route. Nothing about calling it with a fresh breaker and checking "did it end up open" catches this; you have to count exactly how many failures it took.
Suggested approach
Look at the comparison operator in on_failure() against what "threshold" should mean: is a failure_threshold of N the number of failures that are still tolerated, or the number of failures that trips it?
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_09_breaker_threshold_off_by_one.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/breaker-threshold-off-by-oneFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/breaker-threshold-off-by-oneSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080fast_echo/echo/.../large/{size_mb}slow_upstream/delay/{s}/flaky/always-downroutes.yamlcurl localhost:8080/healthzcurl localhost:8080/readyz/admin/*Authorization: Bearer <GATEWAY_ADMIN_TOKEN>You do not need Docker running at all to work the tickets — the test suite fakes Redis with fakeredis (plus lupa for real Lua-script support), so everything runs against a plain virtualenv:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/pytest practice_tickets/tests/test_ticket_09_breaker_threshold_off_by_one.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
./practice_tickets/run_tickets.sh 06 09 15 # just a few, by number
All 15 tests fail at the start; as you fix each ticket in app/, its test (and only its test) should flip to passing. .venv/bin/pytest tests/ runs the project's own main suite — as of writing it shows 8 pre-existing failures caused by ticket bugs 02, 04, 06, and 09; fixing those tickets fixes those failures too, not a separate task.
Level 1
Fix a bug
Read existing behaviour, correct it.