Skip to content

Commit

Permalink
Improve skip failure count
Browse files Browse the repository at this point in the history
  • Loading branch information
stfndamjanovic committed Jan 26, 2024
1 parent 34705ec commit 7162847
Show file tree
Hide file tree
Showing 10 changed files with 23 additions and 96 deletions.
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,15 @@ $breaker = CircuitBreaker::for('3rd-party-service')
]);
```

## Interceptors
## Excluding some failures

You can configure the circuit breaker to fail based on specific conditions or to skip certain types of failures:
Change the circuit breaker behavior by configuring it to exclude certain types of failures:

```php
use Stfn\CircuitBreaker\CircuitBreaker;

$breaker = CircuitBreaker::for('3rd-party-service')
->failWhen(function ($result) {
return $result->status() >= 400;
});

$breaker = CircuitBreaker::for('3rd-party-service')
->skipFailure(function ($exception) {
->skipFailureCount(function ($exception) {
return $exception->getCode() < 500;
});
```
Expand Down
34 changes: 5 additions & 29 deletions src/CircuitBreaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,7 @@ class CircuitBreaker
/**
* @var \Closure|null
*/
protected \Closure|null $failWhenCallback = null;

/**
* @var \Closure|null
*/
protected \Closure|null $skipFailureCallback = null;
protected \Closure|null $skipFailureCountCallback = null;

/**
* @param string $name
Expand Down Expand Up @@ -200,20 +195,9 @@ public function withListeners(array $listeners): self
* @param \Closure $closure
* @return $this
*/
public function skipFailure(\Closure $closure)
public function skipFailureCount(\Closure $closure)
{
$this->skipFailureCallback = $closure;

return $this;
}

/**
* @param \Closure $closure
* @return $this
*/
public function failWhen(\Closure $closure)
{
$this->failWhenCallback = $closure;
$this->skipFailureCountCallback = $closure;

return $this;
}
Expand Down Expand Up @@ -264,16 +248,8 @@ public function getListeners(): array
/**
* @return \Closure|null
*/
public function getFailWhenCallback(): ?\Closure
{
return $this->failWhenCallback;
}

/**
* @return \Closure|null
*/
public function getSkipFailureCallback(): ?\Closure
public function getSkipFailureCountCallback(): ?\Closure
{
return $this->skipFailureCallback;
return $this->skipFailureCountCallback;
}
}
2 changes: 1 addition & 1 deletion src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Config
* @param int $failureThreshold
* @param int $recoveryTime
* @param int $sampleDuration
* @param int $consecutiveSuccesses
* @param int $consecutiveSuccess
*/
public function __construct(
int $failureThreshold = 5,
Expand Down
2 changes: 1 addition & 1 deletion src/Exceptions/CircuitOpenException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ class CircuitOpenException extends \Exception
*/
public static function make($service)
{
return new self("Circuit breaker for service '{$service}' is open");
return new self("Circuit breaker for service {$service} is open.");
}
}
11 changes: 0 additions & 11 deletions src/Exceptions/FailOnSuccessException.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Exceptions/InvalidStateException.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ class InvalidStateException extends \Exception
*/
public static function make($state)
{
return new self("State {$state} is not valid");
return new self("State {$state} is not valid.");
}
}
8 changes: 3 additions & 5 deletions src/StateHandlers/ClosedStateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ class ClosedStateHandler extends StateHandler
*/
public function onFailure(\Exception $exception)
{
$storage = $this->breaker->getStorage();
$failures = $this->breaker->getStorage()->getCounter()->numberOfFailures();

$storage->incrementFailure();
$threshold = $this->breaker->getConfig()->failureThreshold;

$config = $this->breaker->getConfig();

if ($storage->getCounter()->numberOfFailures() >= $config->failureThreshold) {
if ($failures >= $threshold) {
$this->breaker->openCircuit();

throw CircuitOpenException::make($this->breaker->getName());
Expand Down
18 changes: 5 additions & 13 deletions src/StateHandlers/StateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Stfn\CircuitBreaker\StateHandlers;

use Stfn\CircuitBreaker\CircuitBreaker;
use Stfn\CircuitBreaker\Exceptions\FailOnSuccessException;

class StateHandler
{
Expand Down Expand Up @@ -64,14 +63,16 @@ public function beforeCall(\Closure $action, ...$args)
*/
public function handleFailure(\Exception $exception)
{
if (is_callable($this->breaker->getSkipFailureCallback())) {
$shouldSkip = call_user_func($this->breaker->getSkipFailureCallback(), $exception);
if (is_callable($this->breaker->getSkipFailureCountCallback())) {
$shouldSkip = call_user_func($this->breaker->getSkipFailureCountCallback(), $exception);

if ($shouldSkip) {
return;
throw $exception;
}
}

$this->breaker->getStorage()->incrementFailure();

foreach ($this->breaker->getListeners() as $listener) {
$listener->onFail($this->breaker, $exception);
}
Expand All @@ -84,18 +85,9 @@ public function handleFailure(\Exception $exception)
/**
* @param $result
* @return void
* @throws FailOnSuccessException
*/
public function handleSucess($result)
{
if (is_callable($this->breaker->getFailWhenCallback())) {
$shouldFail = call_user_func($this->breaker->getFailWhenCallback(), $result);

if ($shouldFail) {
throw FailOnSuccessException::make();
}
}

$this->onSucess();

foreach ($this->breaker->getListeners() as $listener) {
Expand Down
30 changes: 4 additions & 26 deletions tests/CircuitBreakerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public function test_if_it_can_handle_function_success()
});

$this->assertEquals($object, $result);

$this->assertTrue($breaker->isClosed());
}

Expand Down Expand Up @@ -202,45 +201,24 @@ public function beforeCall(CircuitBreaker $breaker, \Closure $action, ...$args):
$this->assertEquals(2, $object->count);
}

public function test_if_it_can_skip_some_exception()
public function test_if_it_can_skip_count_of_some_exceptions()
{
$testException = new class () extends \Exception {};

$breaker = CircuitBreaker::for('test-service')
->skipFailure(function (\Exception $exception) use ($testException) {
->skipFailureCount(function (\Exception $exception) use ($testException) {
return $exception instanceof $testException;
});

$this->expectException(\Exception::class);

$breaker->call(function () use ($testException) {
throw $testException;
});

$this->assertEquals(0, $breaker->getStorage()->getCounter()->numberOfFailures());
}

public function test_if_it_can_fail_even_without_exception()
{
$breaker = CircuitBreaker::for('test-service')
->withOptions([
'failure_threshold' => 3,
])
->failWhen(function ($result) {
return $result instanceof \stdClass;
});

foreach (range(1, 3) as $i) {
try {
$breaker->call(fn () => new \stdClass());
} catch (\Exception) {

}
}

// Make sure that number of failures is reset to zero
$this->assertEquals(0, $breaker->getStorage()->getCounter()->numberOfFailures());
$this->assertTrue($breaker->isOpen());
}

public function test_if_it_can_force_open_circuit()
{
$breaker = CircuitBreaker::for('test-service');
Expand Down
1 change: 0 additions & 1 deletion tests/Storage/RedisStorageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public function test_if_increment_failure_will_increase_number_of_failures()

$storage->incrementFailure();


$this->assertEquals(2, $storage->getCounter()->numberOfFailures());
}

Expand Down

0 comments on commit 7162847

Please sign in to comment.