walkor/workerman · error · RuntimeException
Event::addTimer($delay) failed
Error message
Event::addTimer($delay) failed
What it means
This is Workerman's libevent (ext-event) event-loop adapter. delay() schedules one-shot timers by constructing an \Event with TIMEOUT and calling addTimer($delay); libevent returns false when the timer cannot be armed — in practice when the delay is negative (or the event base is broken). The adapter turns that boolean into a RuntimeException so failures are not silently dropped callbacks.
Source
Thrown at src/Events/Event.php:109
} else {
$className = '\EventBase';
}
$this->eventBase = new $className();
}
/**
* {@inheritdoc}
*/
public function delay(float $delay, callable $func, array $args = []): int
{
$className = $this->eventClassName;
$timerId = $this->timerId++;
$event = new $className($this->eventBase, -1, $className::TIMEOUT, function () use ($func, $args, $timerId) {
unset($this->eventTimer[$timerId]);
$this->safeCall($func, $args);
});
if (!$event->addTimer($delay)) {
throw new \RuntimeException("Event::addTimer($delay) failed");
}
$this->eventTimer[$timerId] = $event;
return $timerId;
}
/**
* {@inheritdoc}
*/
public function offDelay(int $timerId): bool
{
if (isset($this->eventTimer[$timerId])) {
$this->eventTimer[$timerId]->del();
unset($this->eventTimer[$timerId]);
return true;
}
return false;
}
View on GitHub (pinned to 1391112a61)
Solutions
- Clamp the interval before scheduling: `$delay = max(0.001, $delay); Timer::add($delay, $fn, [], false);`.
- Find where the negative value originates (deadline math like $deadline - microtime(true)) and guard it at the source.
- If every timer fails even with positive delays, verify the ext-event installation (php --ri event) or force a different loop, e.g. `Worker::$eventLoopClass = Workerman\Events\Ev::class;` (or revolt/Fiber).
Example fix
// before: $delay goes negative once the deadline has passed Timer::add($deadline - microtime(true), $fn, [], false); // RuntimeException: Event::addTimer(-2.5) failed // after: clamp to a minimal positive interval Timer::add(max(0.001, $deadline - microtime(true)), $fn, [], false);
Defensive patterns
Strategy: validation
Validate before calling
function scheduleOnce(float $delay, callable $fn): int
{
if ($delay <= 0) {
$delay = 0.001; // or log and skip
}
return Workerman\Timer::add($delay, $fn, [], false);
} Type guard
function isSchedulableDelay(mixed $delay): bool
{
return is_numeric($delay) && (float)$delay > 0;
} Try / catch
try {
Timer::add($delay, $fn, [], false);
} catch (RuntimeException $e) {
// libevent refused the timer; degrade to immediate execution
$fn();
} Prevention
- Never derive one-shot delays from unbounded differences; clamp with max(0.001, $delay).
- Validate numeric, positive intervals from config before they reach Timer::add.
- Pin a known-good event loop in tests to catch loop-specific timer behavior before production.
When it happens
Trigger: Workerman\Timer::add($t, $fn, $args, false) (persistent=false maps to delay()) with $t <= 0 — typically a negative interval computed from a deadline ('now - startTime' after the deadline passed), or the event extension being misconfigured such that every addTimer fails. Fires only when the loop in use is the Event (libevent) implementation.
Common situations: Retry/backoff code computing 'next attempt time - now' that goes negative under load; passing -1 as a 'no delay' sentinel; crontab-style schedules that compute a negative offset; environments where ext-event is installed (so it is auto-selected) but its event base is unavailable.
Related errors
- Event::addTimer($interval) failed
- $timeInterval can not less than 0
- Timer can only be used in workerman running environment
- %s::$eventLoopClass must implement %s
- Bad remoteAddress
AI-assisted analysis of walkor/workerman@1391112a61 (2026-08-21).
Data as JSON: /api/errors/b590a47a3bb68b5c.
Report an issue: GitHub.