Skip to content

Commit

Permalink
Add force open state
Browse files Browse the repository at this point in the history
  • Loading branch information
stfndamjanovic committed Jan 19, 2024
1 parent e4ff0d4 commit 24a96d9
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 5 deletions.
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ composer require stfndamjanovic/php-circuit-breaker

## Usage

Wrap your potentially error-prone function with the circuit breaker, and it will monitor and handle failures. The circuit breaker tracks the occurrence of exceptions and takes preventive measures, such as opening the circuit, if failures become too frequent. During the circuit open state, calls to the function are temporarily halted, allowing the system to recover.
Wrap your potentially error-prone function with the circuit breaker, and it will monitor and handle failures.

```php
use Stfn\CircuitBreaker\CircuitBreaker;
Expand All @@ -25,7 +25,34 @@ $result = CircuitBreaker::for('3rd-party-service')->call(function () {
});
```

### Storage
## States

Circuit breaker can have 4 different states.

### Closed

In the Closed state, the circuit breaker is fully operational, allowing calls to the 3rd party service. Any exceptions that occur during this state are counted.

### Half Open

The Half Open state is a transitional phase where the circuit breaker allows a limited number of calls to the 3rd party service. If these calls are successful, the circuit is closed again. However, if the service continues to exhibit issues, the circuit is moved back to the `Open` state.

### Open

The `Open` state indicates that the circuit breaker has detected a critical failure, and the call method will fail immediately, throwing a `CircuitOpenException` exception.

### Force Open

`Force Open` is not part of the regular flow. It can be utilized when intentional suspension of calls to a service is required. In this state, a `CircuitForceOpenException` will be thrown.

To force the circuit breaker into the Force Open state, use the following:
```php
use Stfn\CircuitBreaker\CircuitBreaker;

$breaker = CircuitBreaker::for('3rd-party-service')->forceOpenCircuit();
```
This feature provides a manual override to stop calls to a service temporarily, offering additional control when needed.
## Storage

By default, the circuit breaker uses `InMemoryStorage` as a storage driver, which is not suitable for most of PHP applications.

Expand All @@ -49,7 +76,7 @@ $result = CircuitBreaker::for('3rd-party-service')

You could also write your implementation of storage. You should just implement `CircuitBreakerStorage` interface.

### Configuration
## Configuration

Each circuit breaker has default configuration settings, but you can customize them to fit your needs:
```php
Expand All @@ -62,7 +89,7 @@ $breaker = CircuitBreaker::for('3rd-party-service')
]);
```

### Middlewares
## Interceptors

You can configure the circuit breaker to fail based on specific conditions or to skip certain types of failures:

Expand All @@ -80,7 +107,7 @@ $breaker = CircuitBreaker::for('3rd-party-service')
});
```

### Listeners
## Listeners

You can add listeners for circuit breaker actions by extending the CircuitBreakerListener class:

Expand Down
10 changes: 10 additions & 0 deletions src/CircuitBreaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Stfn\CircuitBreaker\Exceptions\InvalidStateException;
use Stfn\CircuitBreaker\StateHandlers\ClosedStateHandler;
use Stfn\CircuitBreaker\StateHandlers\ForceOpenStateHandler;
use Stfn\CircuitBreaker\StateHandlers\HalfOpenStateHandler;
use Stfn\CircuitBreaker\StateHandlers\OpenStateHandler;
use Stfn\CircuitBreaker\StateHandlers\StateHandler;
Expand Down Expand Up @@ -80,6 +81,7 @@ protected function makeStateHandler()
CircuitState::Closed->value => ClosedStateHandler::class,
CircuitState::HalfOpen->value => HalfOpenStateHandler::class,
CircuitState::Open->value => OpenStateHandler::class,
CircuitState::ForceOpen->value => ForceOpenStateHandler::class
];

if (! array_key_exists($state->value, $map)) {
Expand All @@ -105,6 +107,14 @@ public function closeCircuit()
$this->storage->close();
}

/**
* @return void
*/
public function forceOpenCircuit()
{
$this->storage->setState(CircuitState::ForceOpen);
}

/**
* @return bool
*/
Expand Down
1 change: 1 addition & 0 deletions src/CircuitState.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ enum CircuitState: string
case Open = 'open';
case Closed = 'closed';
case HalfOpen = 'half_open';
case ForceOpen = 'force_open';
}
11 changes: 11 additions & 0 deletions src/Exceptions/CircuitForceOpenException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Stfn\CircuitBreaker\Exceptions;

class CircuitForceOpenException extends \Exception
{
public static function make()
{
return new self("The circuit is manually opened.");
}
}
19 changes: 19 additions & 0 deletions src/StateHandlers/ForceOpenStateHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Stfn\CircuitBreaker\StateHandlers;

use Stfn\CircuitBreaker\Exceptions\CircuitForceOpenException;

class ForceOpenStateHandler extends StateHandler
{
/**
* @param \Closure $action
* @param ...$args
* @return void
* @throws CircuitForceOpenException
*/
public function beforeCall(\Closure $action, ...$args)
{
throw CircuitForceOpenException::make();
}
}
11 changes: 11 additions & 0 deletions tests/CircuitBreakerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Stfn\CircuitBreaker\CircuitBreaker;
use Stfn\CircuitBreaker\CircuitBreakerListener;
use Stfn\CircuitBreaker\CircuitState;
use Stfn\CircuitBreaker\Exceptions\CircuitForceOpenException;
use Stfn\CircuitBreaker\Exceptions\CircuitHalfOpenFailException;
use Stfn\CircuitBreaker\Exceptions\CircuitOpenException;

Expand Down Expand Up @@ -196,4 +197,14 @@ public function test_if_it_can_fail_even_without_exception()
$this->assertEquals(0, $breaker->getStorage()->getFailuresCount());
$this->assertTrue($breaker->isOpen());
}

public function test_if_it_can_force_open_circuit()
{
$breaker = CircuitBreaker::for('test-service');
$breaker->forceOpenCircuit();

$this->expectException(CircuitForceOpenException::class);

$breaker->call(fn() => true);
}
}

0 comments on commit 24a96d9

Please sign in to comment.