skip to content

Under Laravel Octane on Swoole, how does Octane::tick() with seconds() and immediate() schedule a callback, and where does that callback run?

level: middleimportance: nice to knowfreq 15%

answer

  1. register in a provider's boot()
  2. master timer every second
  3. tick handled by a task worker
  4. last run stored in octane cache
  5. tick() already defaults immediate to true

basics

~10 s

Octane::tick('name', $callback)->seconds(10), registered in a provider's boot(), listens for TickReceived. The Swoole master sends a tick task each second and a task worker runs due callbacks. tick() defaults immediate to true.

solid answer

~40 s

`Octane::tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true)` returns an `InvokeTickCallable` whose `seconds()` and `immediate()` set its options, and registers it as a listener for Octane's `TickReceived` event; the docs say to call it in a service provider's `boot()`. On Swoole, the master process runs a one-second timer that sends an `octane-tick` task; the task worker that receives it dispatches `TickReceived`, and each tick listener checks the `octane` cache for `tick-<key>`. If the interval has passed it records the time and calls the callback; exceptions are reported, not thrown. Because the source defaults `$immediate` to `true`, the first tick runs the callback even without `->immediate()`; pass `false` as the fourth argument to wait one interval first. Ticks share the task worker pool and run once per server, not per worker.

code

php · 20 lines
php
<?php

namespace App\Providers;

use App\Support\Rates;
use Illuminate\Support\ServiceProvider;
use Laravel\Octane\Facades\Octane;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Runs on the first tick, then every 10 seconds
        Octane::tick('refresh-rates', fn () => Rates::refresh())
            ->seconds(10);

        // Waits 60 seconds before the first run
        Octane::tick('prune-sessions', fn () => Rates::prune(), 60, false);
    }
}

go deeper

for a junior

Recall that Octane::tick registers a callback that runs every N seconds on Swoole, set with seconds(), usually in a provider's boot().

for a middle

Explain the mechanics: the master's one-second timer, the tick task on a task worker, the octane cache timestamp, and the immediate default.

for a senior

Show you keep tick callbacks short, account for the shared task pool, and know ticks run per server and vanish on restart.

for a principal

Choose between per-server in-memory refreshes and scheduled or queued work, weighing freshness, fleet-wide duplication and durability.

## What a tick is A **tick** in **Laravel Octane** is a callback the server runs on an interval of seconds, for as long as the server is up - refreshing an in-memory value, sampling metrics, pruning a Swoole table. It is a Swoole-only feature. ```php Octane::tick('refresh-rates', fn () => Rates::refresh()) ->seconds(10); ``` The method signature in Octane 2.20 is: ```php tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true) ``` It returns an `InvokeTickCallable` with two fluent setters: `seconds(int $seconds)` and `immediate()`. Octane's docs say to register ticks in the `boot()` method of a service provider. ## The machinery, step by step 1. **Registration.** `Octane::tick()` creates the `InvokeTickCallable` and registers it with Laravel's event dispatcher as a listener for `Laravel\Octane\Events\TickReceived`. Because providers boot in every worker, every worker registers the same listener. 2. **The clock.** When the Swoole server starts, the master process starts a timer that fires every **1000 ms** and calls `$server->task('octane-tick')`. 3. **The worker.** A **task worker** receives that task and calls the Octane worker's `handleTick()`, which clones the application and dispatches `TickReceived` (followed by `TickTerminated`). 4. **The decision.** Each tick listener reads `tick-<key>` from the `octane` cache store - a Swoole table shared by all workers. If less than `seconds` has passed since the stored timestamp, it returns. Otherwise it stores the current timestamp and calls your callback. 5. **Errors.** Exceptions from the callback are passed to Laravel's exception handler's `report()`; they do not stop the timer. Because the clock is in the master and the last-run time is in shared memory, a tick runs about **once per interval per server**, not once per worker. On a fleet of five servers, each server runs its own ticks, five times in total. ## `immediate()` and the first run The first time a tick listener sees no stored timestamp, it records the time and then: - runs the callback if `immediate` is true; - skips the run if `immediate` is false, so the first call happens one interval later. The docs present `->immediate()` as the way to run on server start. In the 2.20 source, `tick()` already defaults `$immediate` to **true**, so the callback runs on the first tick with or without `->immediate()`. To delay the first run, pass `false` as the fourth argument: `Octane::tick('key', $callback, 10, false)`. ## Practical constraints - **Resolution is one second.** Intervals are whole seconds, checked on each master tick. - **Ticks compete for task workers.** The tick is an ordinary task; if `Octane::concurrently` calls occupy every task worker, ticks are delayed. - **A slow callback blocks a task worker** for its duration, and the next tick may pick another worker, so keep callbacks short. - **The octane cache must exist.** The tick bookkeeping lives in `Cache::store('octane')`, so the `cache` table in `config/octane.php` must not be removed. - **Ticks stop with the server.** Nothing persists across restarts; the first tick after a restart counts as a first run again. ## Ticks versus the scheduler | | Octane tick | Laravel scheduler | |---|---|---| | Resolution | whole seconds | cron expressions, down to sub-minute frequencies | | Runs on | every Swoole server, once per interval each | wherever `schedule:run` or `schedule:work` runs | | Survives restarts | no; timing restarts with the server | yes; driven by the clock | | Needs | Swoole, task workers, the `octane` store | any runtime | Use ticks for keeping **in-memory, per-server** state fresh; use the scheduler for application jobs. (Scheduling itself is a separate topic.) ## Good and bad uses - Good: refreshing a per-server snapshot of remote configuration, pruning expired rows from a Swoole table, sampling worker metrics. - Bad: sending emails, charging cards or anything that must happen exactly once across the fleet, since every server runs its own tick.

  • Why does a tick run once per server rather than once per worker?
    Only the Swoole master's timer triggers ticks, sending one `octane-tick` task per second to a single task worker. The listener also stores its last run time in the `octane` cache, a Swoole table shared by every worker on that server, so a second listener would see the interval has not elapsed.
  • What happens when a tick callback throws?
    The `InvokeTickCallable` catches the throwable and passes it to the exception handler's `report()`. The timestamp was already stored before the call, so the next attempt waits a full interval, and the timer keeps running for this and other ticks.

saying these in an interview costs you the question

  • A tick runs in every HTTP worker, so it fires once per worker
  • Without ->immediate() the first run always waits one interval in 2.20
  • Ticks survive a server restart like scheduled tasks do
  • A throwing tick callback stops the tick timer
  • Octane::tick works the same on FrankenPHP and RoadRunner