Simple Event Dispatcher
A lightweight event system with priority-based listeners and error isolation per handler.
Automatically retry failed operations with increasing delays and jitter to prevent thundering herd problems.
function retryWithBackoff(
callable $operation,
int $maxAttempts = 3,
int $baseDelayMs = 100
): mixed {
$lastException = null;
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
return $operation();
} catch (Throwable $e) {
$lastException = $e;
if ($attempt === $maxAttempts) {
break;
}
// Exponential backoff: 100ms, 200ms, 400ms...
$delayMs = $baseDelayMs * (2 ** ($attempt - 1));
// Add jitter (±25%) to prevent thundering herd
$jitter = $delayMs * 0.25;
$delayMs += random_int((int) -$jitter, (int) $jitter);
usleep($delayMs * 1000);
}
}
throw $lastException;
}
Let us trace what happens when an API call fails twice then succeeds on the third attempt:
Attempt 1: Call API → Fails (timeout)
→ Wait ~100ms (base delay)
Attempt 2: Call API → Fails (server error)
→ Wait ~200ms (doubled)
Attempt 3: Call API → Succeeds
→ Return result immediately
Total wait: ~300ms
Without retry: immediate failure, manual intervention needed
The jitter is critical. Without it, if 100 clients all fail at the same time, they all retry at the same time — overwhelming the server again. Jitter spreads the retries randomly across a time window, giving the server breathing room to recover.
Without retry:
API hiccup → Pipeline fails → Manual restart → Data delayed
With retry + backoff:
API hiccup → Auto-retry in 100ms → Succeeds → Pipeline continues
Transient failures become invisible to the user
// Retry an API call up to 3 times with exponential backoff
$response = retryWithBackoff(
fn() => $httpClient->post('/api/data', $payload),
maxAttempts: 3,
baseDelayMs: 200
);
// Retry a database connection
$pdo = retryWithBackoff(
fn() => new PDO($dsn, $user, $pass),
maxAttempts: 5,
baseDelayMs: 500
);
Use this for any operation that can fail transiently: API calls, database connections, file locks, external service requests. Do not use it for operations that fail deterministically — retrying bad input three times will not make it valid.