Add circuit breaker support to RedisPool#117
Conversation
Greptile SummaryThis PR adds an optional
Confidence Score: 3/5The production implementation is reasonable, but the only test covering the new circuit-breaker path does not actually exercise it and will throw rather than pass. The guard() helper and fallback wiring in RedisPool.php are logically sound, but the test that was added to validate this feature is broken: it relies on the circuit opening before the assertion runs, which cannot happen with the default failure threshold of 3 and a single check() call. Merging leaves the new code path effectively untested. tests/Abuse/RedisPoolCircuitBreakerTest.php needs the CircuitBreaker constructed with threshold: 1 (or the breaker pre-tripped) before the assertion will behave as intended. Important Files Changed
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
tests/Abuse/RedisPoolCircuitBreakerTest.php:29-33
**Test uses a breaker that won't open in time**
`new CircuitBreaker()` defaults to `threshold: 3`, meaning the circuit stays CLOSED for the first two failures and re-throws the exception on each. `$abuse->check()` triggers exactly one `guard()` call (via `count()`), so the pool throws once, the circuit records one failure (1 < 3), and the exception propagates through `count()` → `check()` → the test instead of returning `false`. The assertion is never reached.
The test should construct the breaker with `threshold: 1` so the circuit opens on the very first failed pool call and the fallback (`0`) is returned immediately.
### Issue 2 of 2
src/Abuse/Adapters/TimeLimit/RedisPool.php:84-101
**Count incremented unconditionally when circuit-open write is skipped**
When the circuit is open, `guard()` returns `null` without writing to Redis, but `$this->count = ($this->count ?? 0) + 1` still runs on line 101. Within the same request this inflates the cached count to 1 (matching the limit), which would make a second `check()` call on the same adapter instance report abuse (`count >= limit`) even though nothing was recorded in Redis. This is a latent inconsistency: if callers reuse the same adapter for more than one check per request cycle, the second call will always report abuse when the circuit is open, regardless of actual Redis state.
Reviews (1): Last reviewed commit: "Add circuit breaker support to RedisPool" | Re-trigger Greptile |
| new CircuitBreaker() | ||
| ); | ||
|
|
||
| $abuse = new Abuse($adapter); | ||
|
|
There was a problem hiding this comment.
Test uses a breaker that won't open in time
new CircuitBreaker() defaults to threshold: 3, meaning the circuit stays CLOSED for the first two failures and re-throws the exception on each. $abuse->check() triggers exactly one guard() call (via count()), so the pool throws once, the circuit records one failure (1 < 3), and the exception propagates through count() → check() → the test instead of returning false. The assertion is never reached.
The test should construct the breaker with threshold: 1 so the circuit opens on the very first failed pool call and the fallback (0) is returned immediately.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/Abuse/RedisPoolCircuitBreakerTest.php
Line: 29-33
Comment:
**Test uses a breaker that won't open in time**
`new CircuitBreaker()` defaults to `threshold: 3`, meaning the circuit stays CLOSED for the first two failures and re-throws the exception on each. `$abuse->check()` triggers exactly one `guard()` call (via `count()`), so the pool throws once, the circuit records one failure (1 < 3), and the exception propagates through `count()` → `check()` → the test instead of returning `false`. The assertion is never reached.
The test should construct the breaker with `threshold: 1` so the circuit opens on the very first failed pool call and the fallback (`0`) is returned immediately.
How can I resolve this? If you propose a fix, please make it concise.| @@ -76,7 +96,7 @@ protected function hit(string $key, int $timestamp): void | |||
| $this->discard($redis); | |||
| throw new RuntimeException('Redis transaction failed.'); | |||
| } | |||
| }); | |||
| }, null); | |||
|
|
|||
| $this->count = ($this->count ?? 0) + 1; | |||
There was a problem hiding this comment.
Count incremented unconditionally when circuit-open write is skipped
When the circuit is open, guard() returns null without writing to Redis, but $this->count = ($this->count ?? 0) + 1 still runs on line 101. Within the same request this inflates the cached count to 1 (matching the limit), which would make a second check() call on the same adapter instance report abuse (count >= limit) even though nothing was recorded in Redis. This is a latent inconsistency: if callers reuse the same adapter for more than one check per request cycle, the second call will always report abuse when the circuit is open, regardless of actual Redis state.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Abuse/Adapters/TimeLimit/RedisPool.php
Line: 84-101
Comment:
**Count incremented unconditionally when circuit-open write is skipped**
When the circuit is open, `guard()` returns `null` without writing to Redis, but `$this->count = ($this->count ?? 0) + 1` still runs on line 101. Within the same request this inflates the cached count to 1 (matching the limit), which would make a second `check()` call on the same adapter instance report abuse (`count >= limit`) even though nothing was recorded in Redis. This is a latent inconsistency: if callers reuse the same adapter for more than one check per request cycle, the second call will always report abuse when the circuit is open, regardless of actual Redis state.
How can I resolve this? If you propose a fix, please make it concise.
Summary
RedisPoolto accept an optional circuit breakerTests
php -l src/Abuse/Adapters/TimeLimit/RedisPool.phpphp -l tests/Abuse/RedisPoolCircuitBreakerTest.phpvendor\bin\phpunit.bat --filter RedisPoolCircuitBreakerTestcomposer lintcomposer formatcomposer checkcomposer validate --strict --no-check-publishgit diff --check