walkor/workerman · error · RuntimeException
Timer can only be used in workerman running environment
Error message
Timer can only be used in workerman running environment
What it means
Timer::add() needs either an installed event loop (set via Timer::init by a running Worker) or Worker instances registered in the process. When both are absent - i.e. the code is executing outside a Workerman runtime - it cannot schedule anything and throws instead of pretending to work.
Source
Thrown at src/Timer.php:157
* @return int
*/
public static function add(float $timeInterval, callable $func, ?array $args = [], bool $persistent = true): int
{
if ($timeInterval < 0) {
throw new RuntimeException('$timeInterval can not less than 0');
}
if ($args === null) {
$args = [];
}
if (self::$event) {
return $persistent ? self::$event->repeat($timeInterval, $func, $args) : self::$event->delay($timeInterval, $func, $args);
}
// If not workerman runtime just return.
if (!Worker::getAllWorkers()) {
throw new RuntimeException('Timer can only be used in workerman running environment');
}
if (empty(self::$tasks)) {
pcntl_alarm(1);
}
$runTime = (int)floor(time() + $timeInterval);
if (!isset(self::$tasks[$runTime])) {
self::$tasks[$runTime] = [];
}
self::$timerId = self::$timerId == PHP_INT_MAX ? 1 : ++self::$timerId;
self::$status[self::$timerId] = true;
self::$tasks[$runTime][self::$timerId] = [$func, (array)$args, $persistent, $timeInterval];
return self::$timerId;
}
View on GitHub (pinned to 1391112a61)
Solutions
- Move Timer::add() calls into worker lifecycle callbacks, typically onWorkerStart, where the event loop exists
- If you need a timer outside Workerman, use a different mechanism (pcntl_alarm, usleep loop, sched/cron) guarded by an environment check
- For tests, initialize the loop explicitly (Timer::init(new Workerman\Events\Select())) or mock the Timer calls
Example fix
// before
Timer::add(1, fn() => echo 'tick'); // top of script, no worker running -> throws
$worker = new Worker('http://0.0.0.0:8080');
Worker::runAll();
// after
$worker = new Worker('http://0.0.0.0:8080');
$worker->onWorkerStart = function () {
Timer::add(1, fn() => echo 'tick'); // loop is ready here
};
Worker::runAll(); Defensive patterns
Strategy: validation
Validate before calling
if (!Timer::initialized() && !Worker::getAllWorkers()) { // guard before scheduling
error_log('Timer skipped: not running under Workerman');
return;
}
Timer::add(1, $cb); Prevention
- Only call Timer::add from worker callbacks (onWorkerStart/onConnect/onMessage), never from top-level bootstrap
- Give shared libraries an explicit runtime flag so the same file can run under Workerman and standalone cron
When it happens
Trigger: Calling Timer::add() in a plain CLI script or unit test that never starts workers; calling it in bootstrap code before Worker::runAll()/Worker::run() has initialized the event loop; a shared library file used both by Workerman workers and standalone cron scripts.
Common situations: Running part of the application via 'php bin/job.php' for debugging; PHPUnit bootstraps that touch code paths using Timer; composer post-install scripts reusing app code; calling Timer::add at file top-level instead of inside a worker callback.
Related errors
- $timeInterval can not less than 0
- Event::addTimer($delay) failed
- Event::addTimer($interval) failed
- Bad remoteAddress
- Invalid protocol scheme '$scheme'
AI-assisted analysis of walkor/workerman@1391112a61 (2026-08-21).
Data as JSON: /api/errors/5773024d6ee79cb7.
Report an issue: GitHub.