- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
204 lines
6.9 KiB
PHP
204 lines
6.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\ErrorBoundaries\CircuitBreaker;
|
|
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\ErrorBoundaries\BoundaryConfig;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\StateManagement\StateManager;
|
|
|
|
/**
|
|
* Circuit breaker manager for error boundaries using generic state management
|
|
*/
|
|
final readonly class BoundaryCircuitBreakerManager
|
|
{
|
|
public function __construct(
|
|
/**
|
|
* @var StateManager<BoundaryCircuitBreakerState>
|
|
*/
|
|
private StateManager $stateManager,
|
|
private ?Logger $logger = null,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Check if circuit is open for given boundary
|
|
*/
|
|
public function isCircuitOpen(string $boundaryName, BoundaryConfig $config): bool
|
|
{
|
|
$state = $this->getCircuitState($boundaryName);
|
|
|
|
if ($state->isOpen()) {
|
|
// Check if circuit should transition to half-open
|
|
if ($this->shouldTransitionToHalfOpen($state, $config)) {
|
|
$newState = $state->transitionToHalfOpen();
|
|
$this->stateManager->setState($boundaryName, $newState);
|
|
$this->log('info', "Circuit transitioned to HALF_OPEN for boundary: {$boundaryName}");
|
|
|
|
return false; // Allow operations in half-open state
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Record a successful operation
|
|
*/
|
|
public function recordSuccess(string $boundaryName, BoundaryConfig $config): void
|
|
{
|
|
$newState = $this->stateManager->updateState(
|
|
$boundaryName,
|
|
function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState {
|
|
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
|
$newState = $state->recordSuccess();
|
|
|
|
// If in half-open state and enough successes, close the circuit
|
|
if ($state->isHalfOpen() && $newState->meetsSuccessThreshold($config->successThreshold ?? 3)) {
|
|
$newState = $newState->transitionToClosed();
|
|
$this->log('info', "Circuit transitioned to CLOSED for boundary: {$boundaryName}");
|
|
}
|
|
|
|
return $newState;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Record a failed operation
|
|
*/
|
|
public function recordFailure(string $boundaryName, BoundaryConfig $config): void
|
|
{
|
|
$newState = $this->stateManager->updateState(
|
|
$boundaryName,
|
|
function (?BoundaryCircuitBreakerState $currentState) use ($config, $boundaryName): BoundaryCircuitBreakerState {
|
|
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
|
$newState = $state->recordFailure();
|
|
|
|
// Check if failure threshold is exceeded
|
|
if ($newState->exceedsFailureThreshold($config->circuitBreakerThreshold)) {
|
|
$newState = $newState->transitionToOpen();
|
|
$this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} after {$config->circuitBreakerThreshold} failures");
|
|
}
|
|
|
|
// If in half-open state, any failure should open the circuit
|
|
if ($state->isHalfOpen()) {
|
|
$newState = $newState->transitionToOpen();
|
|
$this->log('warning', "Circuit transitioned to OPEN for boundary: {$boundaryName} due to failure in HALF_OPEN state");
|
|
}
|
|
|
|
return $newState;
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get current circuit state
|
|
*/
|
|
public function getCircuitState(string $boundaryName): BoundaryCircuitBreakerState
|
|
{
|
|
return $this->stateManager->getState($boundaryName) ?? new BoundaryCircuitBreakerState();
|
|
}
|
|
|
|
/**
|
|
* Reset circuit breaker state
|
|
*/
|
|
public function resetCircuit(string $boundaryName): void
|
|
{
|
|
$newState = new BoundaryCircuitBreakerState(); // Default closed state
|
|
$this->stateManager->setState($boundaryName, $newState);
|
|
$this->log('info', "Circuit reset for boundary: {$boundaryName}");
|
|
}
|
|
|
|
/**
|
|
* Get all circuit breaker states
|
|
*/
|
|
public function getAllCircuitStates(): array
|
|
{
|
|
$states = $this->stateManager->getAllStates();
|
|
$result = [];
|
|
|
|
foreach ($states as $boundaryName => $state) {
|
|
$result[$boundaryName] = $this->getCircuitHealth($boundaryName);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check circuit health for monitoring
|
|
*/
|
|
public function getCircuitHealth(string $boundaryName): array
|
|
{
|
|
$state = $this->getCircuitState($boundaryName);
|
|
|
|
return [
|
|
'boundary_name' => $boundaryName,
|
|
'state' => $state->state->value,
|
|
'state_description' => $state->state->getDescription(),
|
|
'failure_count' => $state->failureCount,
|
|
'success_count' => $state->successCount,
|
|
'last_failure_time' => $state->lastFailureTime?->toIsoString(),
|
|
'opened_at' => $state->openedAt?->toIsoString(),
|
|
'half_open_attempts' => $state->halfOpenAttempts,
|
|
'is_healthy' => $state->isClosed(),
|
|
'severity' => $state->state->getSeverity(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get state manager statistics
|
|
*/
|
|
public function getStatistics(): array
|
|
{
|
|
return $this->stateManager->getStatistics()->toArray();
|
|
}
|
|
|
|
/**
|
|
* Increment half-open attempts
|
|
*/
|
|
public function incrementHalfOpenAttempts(string $boundaryName): void
|
|
{
|
|
$this->stateManager->updateState(
|
|
$boundaryName,
|
|
function (?BoundaryCircuitBreakerState $currentState): BoundaryCircuitBreakerState {
|
|
$state = $currentState ?? new BoundaryCircuitBreakerState();
|
|
|
|
return $state->incrementHalfOpenAttempts();
|
|
}
|
|
);
|
|
}
|
|
|
|
private function shouldTransitionToHalfOpen(BoundaryCircuitBreakerState $state, BoundaryConfig $config): bool
|
|
{
|
|
if (! $state->isOpen() || $state->openedAt === null) {
|
|
return false;
|
|
}
|
|
|
|
$timeSinceOpened = Timestamp::now()->diff($state->openedAt);
|
|
|
|
return $timeSinceOpened->isGreaterThan($config->circuitBreakerTimeout);
|
|
}
|
|
|
|
private function log(string $level, string $message, array $context = []): void
|
|
{
|
|
if ($this->logger === null) {
|
|
return;
|
|
}
|
|
|
|
$context['component'] = 'BoundaryCircuitBreakerManager';
|
|
|
|
match ($level) {
|
|
'debug' => $this->logger->debug("[ErrorBoundary] {$message}", $context),
|
|
'info' => $this->logger->info("[ErrorBoundary] {$message}", $context),
|
|
'warning' => $this->logger->warning("[ErrorBoundary] {$message}", $context),
|
|
'error' => $this->logger->error("[ErrorBoundary] {$message}", $context),
|
|
default => $this->logger->info("[ErrorBoundary] {$message}", $context),
|
|
};
|
|
}
|
|
}
|