{"record":{"id":"d3edbaf76e295824","repo":"walkor/workerman","slug":"event-addtimer-interval-failed","errorCode":null,"errorMessage":"Event::addTimer($interval) failed","messagePattern":"Event::addTimer\\(\\$interval\\) failed","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Events/Event.php","lineNumber":147,"sourceCode":"     * {@inheritdoc}\n     */\n    public function offRepeat(int $timerId): bool\n    {\n        return $this->offDelay($timerId);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function repeat(float $interval, callable $func, array $args = []): int\n    {\n        $className = $this->eventClassName;\n        $timerId = $this->timerId++;\n        $event = new $className($this->eventBase, -1, $className::TIMEOUT | $className::PERSIST, function () use ($func, $args) {\n            $this->safeCall($func, $args);\n        });\n        if (!$event->addTimer($interval)) {\n            throw new \\RuntimeException(\"Event::addTimer($interval) failed\");\n        }\n        $this->eventTimer[$timerId] = $event;\n        return $timerId;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function onReadable($stream, callable $func): void\n    {\n        $className = $this->eventClassName;\n        $fdKey = (int)$stream;\n        $event = new $className($this->eventBase, $stream, $className::READ | $className::PERSIST, $func);\n        if ($event->add()) {\n            $this->readEvents[$fdKey] = $event;\n        }\n    }\n","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/walkor/workerman/blob/1391112a61d23020e11e7b89f17050f6cfaea431/src/Events/Event.php#L129-L165","documentation":"The repeat() half of the same libevent adapter: it arms a PERSIST \\Event timer with addTimer($interval) and throws when libevent refuses the value. The overwhelming cause is a negative or otherwise invalid interval passed through Workerman\\Timer::add($interval, $func, $args, true) (the default persistent mode). Because repeat() timers drive heartbeats and periodic tasks, an exception here aborts the callback registration at runtime.","triggerScenarios":"Timer::add() with persistent=true (default) and a non-positive interval — e.g. interval read from config as 0/-1, an interval computed as a difference that went negative, or interval passed as a string like '-5'. Only with the ext-event Event loop selected.","commonSituations":"Heartbeat/keepalive tasks where the interval comes from per-connection settings and can be unset or zero; cron-like next-run calculations; unit-tested code running under a loop that behaves differently than the production libevent loop.","solutions":["Validate/clamp before registering: `$interval = max(0.001, (float)$interval); Timer::add($interval, $fn);`.","Reject non-positive config values at load time instead of letting them reach the timer (fail fast with a clear config error).","If positive intervals also fail, check ext-event health (php --ri event) or switch loop implementations via Worker::$eventLoopClass."],"exampleFix":"// before: interval from config can be 0 or negative\nTimer::add((float)$config['heartbeat_interval'], [$this, 'ping']);\n// RuntimeException: Event::addTimer(0) failed / Event::addTimer(-1) failed\n\n// after: validate at load time, clamp at use time\n$interval = (float)($config['heartbeat_interval'] ?? 0);\nif ($interval <= 0) {\n    throw new InvalidArgumentException('heartbeat_interval must be > 0');\n}\nTimer::add($interval, [$this, 'ping']);","handlingStrategy":"validation","validationCode":"function addIntervalTimer(float $interval, callable $fn): int\n{\n    if ($interval <= 0) {\n        throw new InvalidArgumentException('interval must be > 0, got ' . var_export($interval, true));\n    }\n    return Workerman\\Timer::add($interval, $fn); // persistent=true -> repeat()\n}","typeGuard":"function isSchedulableInterval(mixed $interval): bool\n{\n    return is_numeric($interval) && (float)$interval > 0;\n}","tryCatchPattern":"try {\n    Timer::add($interval, $fn);\n} catch (RuntimeException $e) {\n    Worker::log('timer registration failed: ' . $e->getMessage());\n    throw $e; // do not silently drop periodic tasks (heartbeats, flushers)\n}","preventionTips":["Treat heartbeat/interval config as validated input: positive float, cast once at load.","Default missing interval config to a sane constant instead of passing 0/null through.","Smoke-test the production event loop (ext-event) in CI so addTimer failures surface before deploy."],"tags":["workerman","timer","libevent","event-loop","php"],"backgroundTag":"invalid-timer-interval","analyzedSha":"1391112a61d23020e11e7b89f17050f6cfaea431","analyzedAt":"2026-08-21T02:05:46.744Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}