Timers
Server-level recurring tasks via OpenSwoole\Timer. Each worker runs its own timers — use App::onWorkerStart() to register them.
Recurring timer in every worker
// In app.php (before run()):
$tickCounter = new Counter(0);
App::onWorkerStart(function($server, $workerId) use ($tickCounter) {
// Starts 1 timer per worker; N workers = N timers, all incrementing
App::tick(2000, function() use ($tickCounter) {
$tickCounter->increment();
});
});
// One-shot timer from a route:
$app->route('/timers/oneshot', function($response) {
$response->stream(function($write) {
$result = new Channel(1);
App::after(3000, fn() => $result->push('done after 3s'));
$write($result->pop(5));
});
});
SSE — /timers/sse
$app->route('/timers/sse', function($response) use ($tickCounter, $requestCounter) {
$requestCounter->increment();
$response->sse(function($emit) use ($tickCounter, $requestCounter) {
$emit(json_encode(['event' => 'connected', 'tick' => $tickCounter->get()]), 'open');
for ($i = 0; $i < 20; $i++) {
usleep(2000000);
$emit(json_encode([
'tick' => $tickCounter->get(),
'requests' => $requestCounter->get(),
'time' => date('H:i:s'),
]), 'tick', (string)$i);
}
$emit(json_encode(['done' => true]), 'done');
});
});
GET
Counter incremented by tick timers
// tick_count = total increments across all workers and 2s intervals
App::onWorkerStart(function($server, $workerId) use ($tickCounter) {
App::tick(2000, fn() => $tickCounter->increment());
});
$app->route('/timers/counter', function() use ($requestCounter, $tickCounter) {
$requestCounter->increment();
return ['requests_served' => $requestCounter->get(), 'tick_count' => $tickCounter->get()];
});
LIVE OUTPUT
Click Run →
GET
One-shot delayed task
$app->route('/timers/oneshot', function($response) use ($requestCounter) {
$requestCounter->increment();
$response->stream(function($write) {
$result = new Channel(1);
App::after(3000, function() use ($result) {
$result->push(['done' => true, 'time' => date('H:i:s'), 'pid' => getmypid()]);
});
$write($result->pop(5));
});
});
LIVE OUTPUT
Click Run →
GET
Per-worker metrics via Store
Store::make('worker_metrics', 64, [
'pid' => [\OpenSwoole\Table::TYPE_INT, 4],
'ticks' => [\OpenSwoole\Table::TYPE_INT, 8],
]);
App::onWorkerStart(function($server, $workerId) use ($tickCounter) {
$pid = getmypid();
Store::set('worker_metrics', (string)$workerId, ['pid' => $pid, 'ticks' => 0]);
App::tick(2000, function() use ($workerId, $tickCounter) {
$tickCounter->increment();
Store::incr('worker_metrics', (string)$workerId, 'ticks');
});
});
LIVE OUTPUT
Click Run →
SSE
/timers/sse — Server-Sent Events// Connect with EventSource and stream tick events.
const es = new EventSource('/timers/sse');
es.addEventListener('tick', e => console.log(JSON.parse(e.data)));
es.addEventListener('done', () => es.close());
LIVE OUTPUT
Click Connect to start…
Timer API
| Method | When to use |
|---|---|
App::tick(int $ms, callable $fn) | Recurring task — runs every $ms milliseconds in this worker |
App::after(int $ms, callable $fn) | One-shot — fires once after $ms milliseconds |
App::clearTimer(int $id) | Cancel a tick/after timer by its returned ID |
App::onWorkerStart(callable $fn) | Register a callback called when each worker starts — right place for timers |
Must be called inside a coroutine context.
App::tick() works inside onWorkerStart callbacks and route handlers, but not at the global PHP scope (before the server starts).