Skip to content
This repository has been archived by the owner on Aug 17, 2022. It is now read-only.

Fixed half open state transition #5

Merged
merged 1 commit into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/PartialCircuitBreaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,19 @@ public function isClosed(): bool
* @param string $state the Place state
* @param string $service the service URI
*
* @return bool
* @return Transaction
*/
protected function moveStateTo($state, $service): bool
protected function moveStateTo($state, $service): Transaction
{
$this->currentPlace = $this->places[$state];
$transaction = SimpleTransaction::createFromPlace(
$this->currentPlace,
$service
);

return $this->storage->saveTransaction($service, $transaction);
$this->storage->saveTransaction($service, $transaction);

return $transaction;
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/SimpleCircuitBreaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public function call(string $service, callable $fallback, array $serviceParamete
{
$transaction = $this->initTransaction($service);

try {
if ($this->isOpened()) {
if ($this->canAccessService($transaction)) {
$this->moveStateTo(States::HALF_OPEN_STATE, $service);
}

if ($this->isOpened()) {
if (!$this->canAccessService($transaction)) {
return (string) $fallback();
}

$transaction = $this->moveStateTo(States::HALF_OPEN_STATE, $service);
}

try {
$response = $this->request($service);
$this->moveStateTo(States::CLOSED_STATE, $service);

Expand Down
22 changes: 11 additions & 11 deletions src/SymfonyCircuitBreaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ public function call(string $service, callable $fallback, array $serviceParamete
{
$transaction = $this->initTransaction($service);

try {
if ($this->isOpened()) {
if ($this->canAccessService($transaction)) {
$this->moveStateTo(States::HALF_OPEN_STATE, $service);
$this->dispatch(
Transitions::CHECKING_AVAILABILITY_TRANSITION,
$service,
$serviceParameters
);
}

if ($this->isOpened()) {
if (!$this->canAccessService($transaction)) {
return (string) $fallback();
}

$transaction = $this->moveStateTo(States::HALF_OPEN_STATE, $service);
$this->dispatch(
Transitions::CHECKING_AVAILABILITY_TRANSITION,
$service,
$serviceParameters
);
}

try {
$response = $this->request($service, $serviceParameters);
$this->moveStateTo(States::CLOSED_STATE, $service);
$this->dispatch(
Expand Down
2 changes: 1 addition & 1 deletion tests/CircuitBreakerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected function getSystem(): MainSystem
return MainSystem::createFromArray(
[
'open' => [0, 0.0, 1.0], // threshold 1.0s
'half_open' => [0, 0.2, 0.0], // timeout 0.2s to test the service
'half_open' => [1, 0.4, 0.0], // timeout 0.4s to test the service with a better timeout
'closed' => [2, 0.2, 0.0], // 2 failures allowed, 0.2s timeout
]
);
Expand Down
80 changes: 24 additions & 56 deletions tests/CircuitBreakerWorkflowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

class CircuitBreakerWorkflowTest extends CircuitBreakerTestCase
{
/**
* @var int the number of seconds to wait before try to reach again the service
*/
private const OPEN_THRESHOLD = 1;

/**
* When we use the circuit breaker on unreachable service
* the fallback response is used.
Expand All @@ -22,7 +27,7 @@ class CircuitBreakerWorkflowTest extends CircuitBreakerTestCase
*/
public function testCircuitBreakerIsInClosedStateAtStart(CircuitBreaker $circuitBreaker): void
{
$this->assertSame('CLOSED', $circuitBreaker->getState());
$this->assertTrue($circuitBreaker->isClosed());

$this->assertSame(
'{}',
Expand All @@ -41,49 +46,22 @@ public function testCircuitBreakerIsInClosedStateAtStart(CircuitBreaker $circuit
* @depends testCircuitBreakerIsInClosedStateAtStart
* @dataProvider getCircuitBreakers
*/
public function testCircuitBreakerWillBeOpenedInCaseOfFailures(CircuitBreaker $circuitBreaker): void
public function testCircuitBreakerWillBeOpenInCaseOfFailures(CircuitBreaker $circuitBreaker): void
{
// CLOSED
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());

$this->assertSame('OPEN', $circuitBreaker->getState());
$this->assertSame(
'{}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);
}

/**
* Once the threshold is reached, the circuit breaker
* try again to reach the service. This time, the service
* is not reachable.
*
* @param CircuitBreaker $circuitBreaker
* @depends testCircuitBreakerIsInClosedStateAtStart
* @depends testCircuitBreakerWillBeOpenedInCaseOfFailures
* @dataProvider getCircuitBreakers
*/
public function testAfterTheThresholdTheCircuitBreakerMovesInHalfOpenState(CircuitBreaker $circuitBreaker): void
{
// CLOSED
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
// OPEN
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertTrue($circuitBreaker->isClosed());
$response = $circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{}', $response);

$this->waitFor(1);
// NOW HALF OPEN
//After two failed calls switch to OPEN state
$this->assertTrue($circuitBreaker->isOpened());
$this->assertSame(
'{}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);
$this->assertSame('HALF OPEN', $circuitBreaker->getState());
$this->assertTrue($circuitBreaker->isHalfOpened());
}

/**
Expand All @@ -92,41 +70,31 @@ public function testAfterTheThresholdTheCircuitBreakerMovesInHalfOpenState(Circu
*
* @param CircuitBreaker $circuitBreaker
* @depends testCircuitBreakerIsInClosedStateAtStart
* @depends testCircuitBreakerWillBeOpenedInCaseOfFailures
* @depends testAfterTheThresholdTheCircuitBreakerMovesInHalfOpenState
* @depends testCircuitBreakerWillBeOpenInCaseOfFailures
* @dataProvider getCircuitBreakers
*/
public function testOnceInHalfOpenModeServiceIsFinallyReachable(CircuitBreaker $circuitBreaker): void
{
// CLOSED
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
// OPEN
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());

$this->waitFor(1);
// NOW HALF OPEN
$this->assertSame(
'{}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);
$this->assertSame('HALF OPEN', $circuitBreaker->getState());
$this->assertTrue($circuitBreaker->isHalfOpened());
// CLOSED - first call fails (twice)
$this->assertTrue($circuitBreaker->isClosed());
$response = $circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{}', $response);
$this->assertTrue($circuitBreaker->isOpened());

$this->waitFor(1);
// OPEN - no call to client
$response = $circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{}', $response);
$this->assertTrue($circuitBreaker->isOpened());
$this->waitFor(2 * self::OPEN_THRESHOLD);

// CLOSED
// SWITCH TO HALF OPEN - retry to call the service
$this->assertSame(
'{"hello": "world"}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);

$this->assertSame('CLOSED', $circuitBreaker->getState());
$this->assertTrue($circuitBreaker->isClosed());
}

Expand Down