skip to content

Server Runtimes & Process Management

The runtimes and supervisors that actually keep a backend process alive — Node, Bun, Deno, .NET — plus the process managers wrapped around them. Interviewers ask because 'it works on my laptop' and 'it survives production' are different problems, and the gap lives here.

on this pageshow

explore

questions

page 1 of 2

Which Laravel Octane features work only on Swoole or Open Swoole, and what happens to each if the app runs on FrankenPHP or RoadRunner?

level: juniorimportance: must knowfreq 30%

answer

  1. four features need Swoole
  2. task workers and Swoole tables
  3. concurrently falls back to sequential
  4. octane store becomes a per-worker array
  5. Octane::table throws outright

basics

~20 s

Concurrent tasks, ticks and intervals, the octane cache store and Swoole tables need Swoole or Open Swoole. Elsewhere Octane::concurrently runs tasks one after another, the octane store is a per-worker array, ticks never fire and Octane::table() throws.

solid answer

~40 s

Octane's docs mark four features as Swoole-only, because they rely on Swoole **task workers** and **Swoole tables** (shared memory): `Octane::concurrently`, `Octane::tick`, the `octane` cache store with `interval()`, and custom tables via `Octane::table()`. Open Swoole provides the same set. On FrankenPHP or RoadRunner they degrade differently: `concurrently()` does not fail but falls back to `SequentialTaskDispatcher`, running the closures one after another in the same process; `Cache::store('octane')` becomes `OctaneArrayStore`, an in-process array per worker, and `interval()` just calls the resolver each time; ticks are never triggered, because the Swoole master's timer is what fires them; and `Octane::table()` throws "Tables may only be accessed when using the Swoole server." The quiet fallbacks are the dangerous part: code keeps working but loses its concurrency and its shared state.

code

php · 14 lines
php
<?php

use Illuminate\Support\Facades\Cache;
use Laravel\Octane\Facades\Octane;

// On FrankenPHP or RoadRunner:
[$a, $b] = Octane::concurrently([
    fn () => slowReportA(),
    fn () => slowReportB(),
]); // runs A, then B, in this process

Cache::store('octane')->put('hits', 1, 60); // this worker's array only

Octane::table('example'); // throws: Swoole server required

go deeper

for a junior

Recall the four Swoole-only features: concurrent tasks, ticks and intervals, the octane cache store, and tables.

for a middle

Explain that they rest on task workers and Swoole tables, and what each does elsewhere: sequential tasks, a per-worker array store, silent ticks, a thrown exception.

for a senior

Show you treat the quiet fallbacks as risks when switching servers and guard code that needs real concurrency or shared state.

for a principal

Decide whether Swoole-only APIs are worth the server lock-in compared with queues, the scheduler and shared cache stores that work on any runtime.

## Why some features need Swoole **Laravel Octane** supports FrankenPHP, RoadRunner and Swoole (or Open Swoole) as servers. Most of Octane - booting the app once, resetting state per request, reload and stop commands - works the same on all of them. Four features do not, because they are built on two things only Swoole's server offers: - **Task workers** - a second pool of worker processes that the Swoole server can hand work to and wait on (`--task-workers`, `auto` by default). - **Swoole tables** - fixed-size tables in shared memory, readable and writable by every worker process on the server. | Feature | Built on | |---|---| | `Octane::concurrently([...])` | task workers (`taskWaitMulti`) | | `Octane::tick(...)` and cache `interval()` refreshes | the master's one-second timer dispatching a tick task | | `Cache::store('octane')` | a Swoole table sized by the `cache` config | | `Octane::table('name')` | Swoole tables defined in the `tables` config | Open Swoole is handled by the same driver (`swoole`); Octane checks for either extension and uses Open Swoole's table class when that extension is loaded. ## What happens on FrankenPHP or RoadRunner The behaviour is not uniform, and only one feature fails loudly. 1. **`Octane::concurrently`** picks a dispatcher at call time. Inside a Swoole server it uses the task workers. If the Swoole extension is merely installed (for example in an Artisan command on a Swoole host) it posts the tasks to the running Octane server over HTTP, falling back to sequential execution if it cannot connect. Otherwise it uses `SequentialTaskDispatcher`, which calls each closure **in turn, in the current process**. The result array looks the same; the time is the sum instead of the maximum. 2. **`Cache::store('octane')`** is registered as an `OctaneStore` over the Swoole cache table when that table exists, and as `OctaneArrayStore` - a subclass of Laravel's array store - when it does not. The array store lives in one worker's memory, so each worker has its own copy, and its `interval()` simply calls the resolver and returns the result every time. 3. **Ticks** are listeners on Octane's `TickReceived` event, which only the Swoole master's timer triggers. Registering `Octane::tick(...)` elsewhere does not error; the callback just never runs. 4. **`Octane::table('name')`** checks that a Swoole server is bound and throws an `Exception` with the message "Tables may only be accessed when using the Swoole server." otherwise. ## Why the quiet fallbacks matter - A dashboard that relied on `concurrently()` for speed gets slower after a server switch, with no error. - Code that used the `octane` store as a **shared** counter or cache across workers now keeps one per worker, so counts and invalidations diverge. - A tick that refreshed a value every few seconds silently stops, so data goes stale. These are exactly the bugs that appear when a team moves from Swoole to FrankenPHP or RoadRunner, or runs the test suite and queue workers outside Octane. ## Choosing whether to use them at all - Using these APIs ties the code to Swoole. Record that as a decision; changing servers later means rewriting those paths. - Laravel offers server-neutral alternatives for some jobs: queued jobs for background work, the scheduler for periodic tasks, a shared cache store for cross-worker data, and the `Concurrency` facade for parallel closures. Each is its own topic; the point here is that they do not depend on the Octane driver. - If you do use them, add a guard in code paths that must not degrade silently, for example checking that a Swoole server is bound before relying on shared state. ## Telling which path you are on Octane decides by checking the container at call time: inside a Swoole server, `Swoole\Http\Server` is bound, and that is what `Octane::table()` and the task dispatcher test for. Code that must not degrade can make the same check itself and fail fast, for example by throwing when a shared counter is requested outside a Swoole server. Logging the running driver at worker start, through a `WorkerStarting` listener, also makes a silent downgrade visible in the logs after a server change. ## A migration story A team runs an Octane app on Swoole, with a per-server request counter in the `octane` store and a tick that refreshes a price list every 30 seconds. They move to FrankenPHP for built-in HTTPS. Nothing errors. But the counter now shows a fraction of the traffic (one worker's share), the price list stops refreshing, and a dashboard using `concurrently()` is three times slower. Each symptom maps to one row of the table above. ## Quick reference - Swoole-only: concurrent tasks, ticks and intervals, the `octane` cache, tables. - Works everywhere: everything else Octane does. - Fails loudly elsewhere: `Octane::table()`. - Degrades quietly elsewhere: `concurrently()` (sequential), the `octane` store (per-worker array), ticks (never fire).

  • Why does Octane::concurrently not throw on FrankenPHP?
    Octane's `tasks()` method chooses a dispatcher at call time: Swoole task workers inside a Swoole server, an HTTP hop to the Octane server when only the extension is present, and otherwise `SequentialTaskDispatcher`. The fallback keeps code portable across servers and in tests, at the cost of running the closures one after another.
  • How would you catch an accidental dependency on Swoole-only behaviour?
    Run the relevant feature tests with Octane off, where the sequential and array fallbacks apply, and assert the behaviour you actually need, such as data shared across requests. For code that must have shared state, check that a Swoole server is bound and fail fast instead of relying on the fallback.

saying these in an interview costs you the question

  • Octane::concurrently throws an exception on FrankenPHP
  • The octane cache store is shared across workers on every Octane server
  • Open Swoole lacks ticks and tables that Swoole has
  • Ticks fall back to Laravel's scheduler when Swoole is absent
  • Octane::table() silently returns an empty table outside Swoole
open as a page

In Laravel Octane, what do octane:install and the OCTANE_SERVER variable decide, and how does octane:start choose which server to launch?

level: juniorimportance: must knowfreq 45%

basics

~10 s

octane:install asks for FrankenPHP, RoadRunner or Swoole, sets up what that server needs, publishes config/octane.php and writes OCTANE_SERVER to .env. octane:start launches --server if given, otherwise config('octane.server'), on 127.0.0.1:8000.

open as a page

Under Laravel Octane, how often do service providers' register() and boot() methods run, and why is reading request() or auth()->user() inside boot() a bug?

level: juniorimportance: must knowfreq 50%

basics

~20 s

Under Octane, providers' register() and boot() run once per worker, at boot. The request then is a synthetic GET built from app.url and nobody is logged in, so whatever boot() reads from request() or auth() is wrong for real requests.

open as a page

After deploying new code to a Laravel Octane server, why do you run octane:reload, and when do you need a full restart instead?

level: middleimportance: must knowfreq 55%

basics

~20 s

Octane workers keep the old code in memory, so a deploy is ignored until they restart; octane:reload restarts them gracefully inside the running server. Options fixed at start - port, worker count, max_execution_time, the driver - need a stop and start.

open as a page

When wiring a PHP-FPM pool to a web server, when should it listen on a Unix socket versus TCP, and how is each secured?

level: middleimportance: must knowfreq 55%

basics

~20 s

Use a Unix socket when the web server and PHP-FPM share a host, secured by listen.owner, listen.group and listen.mode; use TCP across hosts or containers, restricted with listen.allowed_clients and a firewall, because an exposed FastCGI port lets anyone run PHP.

open as a page

A ticket-sales PHP site runs on a 4 GB server under PHP-FPM; how do you size pm.max_children from worker memory?

level: middleimportance: must knowfreq 58%

basics

~20 s

Subtract what the OS, web server, database and other services need, then divide the RAM left for PHP by the measured memory of a busy worker. On 4 GB with about 2.5 GB for PHP and 60 MB per worker, that is roughly 40.

open as a page

In a PHP-FPM pool, how do pm = static, pm = dynamic and pm = ondemand differ, and when would you choose each?

level: middleimportance: must knowfreq 60%

basics

~20 s

static keeps exactly pm.max_children workers alive; dynamic starts pm.start_servers and keeps idle workers between the min and max spare counts; ondemand starts none and forks per demand, killing workers idle past pm.process_idle_timeout (10s). pm.max_children caps all three.

open as a page

After moving a Laravel banking API to Octane, some responses show the previous customer's accounts; which Octane-specific state is carrying them over, and how do you fix it?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Octane clones one booted app per request, but instances resolved at boot or warmed, and static properties, are shared by every request on the worker. A singleton or static cache that memoizes the current customer serves them to the next request.

open as a page

Every PHP request through Nginx to PHP-FPM returns 'File not found.'; what does SCRIPT_FILENAME do, and what usually went wrong?

level: juniorimportance: should knowfreq 45%

basics

~20 s

SCRIPT_FILENAME is the FastCGI parameter telling PHP-FPM which file to execute. 'File not found.' means FPM could not resolve or open that path — usually the parameter is missing or points to a path that does not exist where FPM runs.

open as a page

Under Laravel Octane on Swoole, how do you fetch three dashboards' data in parallel with Octane::concurrently, and where do those closures actually run?

level: middleimportance: should knowfreq 30%

basics

~20 s

Pass Octane::concurrently an array of closures, one per dashboard query, and destructure the returned array; keys are preserved. Each closure is serialized and runs in a Swoole task worker process, so capture only serializable values like IDs.

open as a page

When a Laravel Octane app sits behind an Nginx proxy that terminates TLS, why can it generate http:// links, and what do OCTANE_HTTPS and the proxy config fix?

level: middleimportance: should knowfreq 35%

basics

~20 s

Nginx talks plain HTTP to Octane on 127.0.0.1:8000, so Laravel sees an http request and builds http:// URLs. OCTANE_HTTPS=true makes Octane force the https scheme on every request; Nginx serves static files and proxies the rest to Octane.

open as a page

Under Laravel Octane in local development, why does an edited route not show up until a restart, and what does octane:start --watch need to fix that?

level: middleimportance: should knowfreq 35%

basics

~20 s

Octane workers boot the app once and keep it in memory, so edited files are not reread. octane:start --watch runs a Node chokidar watcher over config('octane.watch') and reloads the workers on change; FrankenPHP uses its own watcher instead.

open as a page

In Laravel Octane, what do the --max-requests option and the garbage setting do about memory growth, and why are neither of them a fix for a leak?

level: middleimportance: should knowfreq 35%

basics

~10 s

--max-requests (default 500) gracefully replaces a worker after that many requests. garbage (50 MB) is read only by the CollectGarbage listener, which ships commented out. Both contain memory growth; neither removes a leak.

open as a page

In Laravel Octane, how do you reset your own static state before each request through the listeners config, and which events can you hook?

level: middleimportance: should knowfreq 25%

basics

~20 s

Add a listener class under RequestReceived in the listeners array of config/octane.php, after Octane's own spread entries; its handle($event) runs before each request and can reset statics. Other hooks include RequestTerminated, OperationTerminated, WorkerStarting and WorkerStopping.

open as a page

Why does getenv('DATABASE_URL') return false in PHP under PHP-FPM when the variable is set in the service's environment?

level: middleimportance: should knowfreq 40%

basics

~20 s

PHP-FPM's clear_env defaults to yes, so workers start with an empty environment plus only the pool's env[] entries. Add env[DATABASE_URL] = $DATABASE_URL to copy it from the master's environment, or set clear_env = no, then reload FPM.

open as a page

In a PHP-FPM pool file, what is the difference between php_value and php_admin_value, and when do you use each?

level: middleimportance: should knowfreq 42%

basics

~20 s

Both set php.ini directives for one PHP-FPM pool. php_value is a default that ini_set() or .user.ini may still change where the directive allows; php_admin_value locks it. Use admin for security and resource limits, plain for defaults.

open as a page

In a PHP-FPM pool with pm = dynamic, how do pm.start_servers, pm.min_spare_servers, pm.max_spare_servers and pm.max_spawn_rate work together?

level: middleimportance: should knowfreq 32%

basics

~10 s

A dynamic PHP-FPM pool starts pm.start_servers workers, then once a second kills one idle worker above pm.max_spare_servers or forks more below pm.min_spare_servers, doubling the spawn rate up to pm.max_spawn_rate (32), never past pm.max_children.

open as a page

What does PHP-FPM's request_terminate_timeout do to a long request, and how does it relate to PHP's max_execution_time?

level: middleimportance: should knowfreq 35%

basics

~20 s

request_terminate_timeout makes the FPM master kill a worker whose request runs past the limit; the client usually gets a 502 and a replacement is forked. Off by default, it backstops PHP's max_execution_time, which raises an error inside the script instead.

open as a page

How do PHP-FPM's request_slowlog_timeout and slowlog work, and what does a slow-log entry show?

level: middleimportance: should knowfreq 38%

basics

~20 s

When a request has run longer than request_slowlog_timeout, the FPM master pauses that worker, reads its PHP call stack and appends it, with the script path, to the pool's slowlog file; the request then continues. It is off by default and needs slowlog set.

open as a page

What do the PHP-FPM status page's active processes, listen queue and max children reached fields tell you about a pool?

level: middleimportance: should knowfreq 42%

basics

~20 s

Active processes counts workers busy right now; listen queue counts connections waiting for a free worker; max children reached counts how often the pool wanted to grow past pm.max_children. Busy workers at the cap plus a non-zero queue means a saturated pool.

open as a page

Under Laravel Octane on Swoole, what happens when a task in Octane::concurrently throws an exception or runs past the wait time, and how should the caller handle it?

level: seniorimportance: should knowfreq 15%

basics

~20 s

A task's exception is rethrown in the caller as Laravel\Octane\Exceptions\TaskException with the original class name, message, file and line. A failed wait (3000 ms default) throws TaskTimeoutException; a task missing from the results comes back as false.

open as a page

For a high-traffic API gateway on Laravel Octane, how do you choose between FrankenPHP, Swoole and RoadRunner, and how many workers do you start?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Swoole or Open Swoole if you need Octane's tasks, ticks, cache or tables; otherwise FrankenPHP (one Go binary with built-in HTTPS) or RoadRunner (a Go process manager). Size --workers from measured memory and I/O wait, not cores alone.

open as a page

Under Laravel Octane, why can a singleton that received the container, the request or the config repository in its constructor see stale values, and what are the fixes?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A singleton built at boot keeps the objects it was given, while Octane gives each request a new request, a cloned config and a cloned container. Inject resolver closures, use app(), request() and config(), or pass values to methods.

open as a page

How would you run two PHP applications under different Unix users behind one Nginx, using PHP-FPM pools?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Define one PHP-FPM pool per application, each with its own user and group, its own listen socket that the Nginx user can open, and locked per-app settings via php_admin_value; each Nginx server block passes PHP requests to its app's socket.

open as a page

During a ticket on-sale, PHP-FPM logs 'server reached pm.max_children setting (40), consider raising it'; what happens to incoming requests, and should you just raise the limit?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The pool is at pm.max_children with idle workers exhausted or below the spare minimum, so new connections queue in the listen backlog until a worker frees up or the web server times out. Raise the cap only with spare memory, CPU and database capacity.

open as a page

What does pm.max_requests do in a PHP-FPM pool, and why is it set to work around memory growth?

level: seniorimportance: should knowfreq 40%

basics

~20 s

pm.max_requests makes each PHP-FPM worker exit after serving that many requests, and the master forks a replacement. The default 0 never recycles. It limits memory that leaks or accumulates inside a long-lived worker, without fixing the cause.

open as a page

During a flash sale, a PHP site behind PHP-FPM returns intermittent 502s; how do you use FPM's status page and logs to find the cause?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A 502 means the web server got no valid FastCGI response. Line up its timestamps with the FPM status page (all workers busy, queue near its length) and the FPM log (terminations, crashes), then read the slow log for what workers wait on.

open as a page

What does PHP-FPM's ping.path endpoint check, and why is it not a full application health check?

level: juniorimportance: nice to knowfreq 22%

basics

~20 s

ping.path makes a PHP-FPM pool answer that URI with a plain-text 'pong' (ping.response) and status 200. It proves the web server can reach the pool and a worker is free, but runs no application code, so it cannot detect a broken database or app.

open as a page

Under Laravel Octane on Swoole, how does Cache::store('octane') store data, how does its interval() method refresh values, and what are the store's limits?

level: middleimportance: nice to knowfreq 15%

basics

~20 s

The octane store keeps serialized values in a Swoole table shared by all workers on one server, sized by cache rows (1000) and bytes (10000), and emptied on restart. interval() registers a resolver the tick refreshes every N seconds.

open as a page

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%

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.

open as a page

showing 1–30 of 35