Under Laravel Octane on Swoole, how does Octane::tick() with seconds() and immediate() schedule a callback, and where does that callback run?
answer
- register in a provider's boot()
- master timer every second
- tick handled by a task worker
- last run stored in octane cache
- tick() already defaults immediate to true
basics
~10 sOctane::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
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
Recall that Octane::tick registers a callback that runs every N seconds on Swoole, set with seconds(), usually in a provider's boot().
Explain the mechanics: the master's one-second timer, the tick task on a task worker, the octane cache timestamp, and the immediate default.
Show you keep tick callbacks short, account for the shared task pool, and know ticks run per server and vanish on restart.
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