In PHP, what is a Fiber, and how do Fiber::start(), Fiber::suspend() and Fiber::resume() pass values between the fiber and its caller?
answer
- a function with its own call stack
- core class since PHP 8.1
- start() runs until the first pause
- suspend's argument comes out of start/resume
- resume's argument comes out of suspend
basics
~20 sA PHP Fiber (core since 8.1) runs a callable on its own call stack that can pause. start() runs it until Fiber::suspend($x), which makes start() return $x; resume($y) continues it and makes that suspend() call return $y.
solid answer
~40 sA `Fiber` wraps a callable in its own call stack so it can stop mid-execution and continue later; it is a core class since PHP 8.1. `$fiber->start(...$args)` calls the callable with those arguments and runs it until the static `Fiber::suspend($value)` is called somewhere inside it. At that moment control jumps back to the caller, and `start()` returns `$value`. Later `$fiber->resume($reply)` jumps back in: the paused `Fiber::suspend()` call returns `$reply`, and `resume()` in turn returns the value of the next suspension. When the callable finally returns, `start()`/`resume()` return `null` and the real return value is read with `getReturn()`. It is a two-way handshake, one side running at a time.
code
php · 16 lines<?php
declare(strict_types=1);
$fiber = new Fiber(function (string $greeting): string {
echo "fiber got: $greeting\n";
$reply = Fiber::suspend('first pause');
echo "fiber resumed with: $reply\n";
$again = Fiber::suspend('second pause');
return "done after $again";
});
echo 'caller got: ', $fiber->start('hello'), "\n"; // first pause
echo 'caller got: ', $fiber->resume('A'), "\n"; // second pause
var_dump($fiber->resume('B')); // NULL: it returned
var_dump($fiber->isTerminated()); // bool(true)
echo $fiber->getReturn(), "\n"; // done after Bgo deeper
Recall that a Fiber is a core class since PHP 8.1, that start() launches it, the static Fiber::suspend() pauses it from inside, and resume() continues it.
Walk the value handshake precisely: what start() and resume() return, what suspend() evaluates to, why the last resume() returns null, and where getReturn() fits.
Explain why application code rarely creates fibers: libraries suspend inside their I/O calls and an event loop decides which fiber to resume, so your code reads as plain synchronous calls.
Frame fibers as a primitive, not a concurrency model: adopting them means adopting a loop library and its non-blocking clients, which is the real architectural decision.
## What a Fiber is A **`Fiber`** is a core PHP class, added in **PHP 8.1** and unchanged in shape through 8.5, that runs a callable on its **own call stack**. Because the fiber has a separate stack, the callable can stop partway through, possibly several function calls deep, and pick up exactly where it stopped later on. The code that creates and drives the fiber is called its **caller**; at any moment either the caller or the fiber is executing, never both. The class is `final`, cannot be cloned and cannot be serialized. You create one with `new Fiber(callable $callback)`; nothing runs until you call `start()`. ## The value handshake, step by step The whole API is a ping-pong of values between two sides: 1. `$fiber->start(...$args)` calls the callable with `$args` as its arguments and runs it. 2. Inside, `Fiber::suspend($x)` pauses the fiber. It is a **static** method: it always pauses whichever fiber is currently running. 3. Control returns to the caller, and the pending `start()` call **returns `$x`**. 4. The caller does other work, then calls `$fiber->resume($y)`. 5. Execution continues inside the fiber, where the paused `Fiber::suspend($x)` expression **evaluates to `$y`**. 6. The fiber runs until its next `Fiber::suspend($z)`, and the caller's `resume($y)` call returns `$z`; or the callable returns, and `resume()` returns `null`. So each switch carries one value: `suspend` hands a value out, `resume` hands a value in. Arguments to the callable itself are passed only once, through `start()`. ## Return values at each call | Call | Who makes it | What it returns | |---|---|---| | `$fiber->start(...$args)` | caller | value given to the first `Fiber::suspend()`, or `null` if the callable returned without suspending | | `Fiber::suspend($value)` | code inside the fiber | the value later passed to `resume()` | | `$fiber->resume($value)` | caller | value given to the next `Fiber::suspend()`, or `null` if the callable returned | | `$fiber->getReturn()` | caller | the callable's `return` value, once it has finished normally | | `Fiber::getCurrent()` | anyone | the running `Fiber`, or `null` outside any fiber | A common slip is to expect `start()` or the last `resume()` to hand back the callable's return value. They cannot, because at that moment they report *why control came back*, and `null` means "it finished". The result lives in `getReturn()`. ## Nesting fibers A fiber can start another fiber. The rule stays the same at every level: `Fiber::suspend()` always pauses the **innermost running fiber** and returns control to whoever last started or resumed it, which may itself be a fiber rather than the main script. `Fiber::getCurrent()` returns that innermost fiber. This is what lets a library run each task in its own fiber while its own scheduling code runs in yet another one: every `resume()` and every `suspend()` is a switch between exactly two parties. ## State you can inspect Four boolean methods report where a fiber is in its life: - `isStarted()` — `start()` has been called at least once. - `isSuspended()` — it is paused inside `Fiber::suspend()` and can be resumed. - `isRunning()` — it is currently executing (you can only observe this from inside it, or from a fiber it started). - `isTerminated()` — the callable returned or threw; the fiber cannot run again. Calling a method in the wrong state, such as `resume()` on a fiber that is not suspended, throws `FiberError`. ## What a Fiber is not - It is **not a thread**. Every fiber in a PHP process runs on the same thread, one at a time, and switches only where someone calls `start()`, `resume()`, `throw()` or `Fiber::suspend()`, or when a fiber finishes. - It is **not a scheduler**. PHP gives you the switching primitive only; deciding *when* to resume which fiber is the job of an event-loop library such as Revolt (used by Amp) or ReactPHP's async package. - It needs **no extension**: `Fiber` is part of the engine, available on every PHP 8.1+ build. In day-to-day application code you rarely write `new Fiber` yourself; libraries create fibers for you and call `Fiber::suspend()` inside their I/O functions. Knowing the handshake explains what those libraries do when your code seems to "wait" without blocking the process.
- What does Fiber::getCurrent() return, and why do libraries call it?It returns the `Fiber` that is executing right now, or `null` when called from the main script outside any fiber. Libraries use it to decide how to wait: inside a fiber they can suspend it and resume it later; outside one there is nothing to suspend, so they must run the event loop themselves until the result arrives.
- What happens if the fiber's callable throws before it ever suspends?The exception propagates out of the `start()` call that switched into the fiber, exactly as if the callable had been called directly. The fiber is then terminated: `isTerminated()` is `true`, it cannot be resumed, and `getReturn()` throws `FiberError` because the fiber threw instead of returning.
saying these in an interview costs you the question
- Fiber::suspend() is called on the fiber object from outside to pause it
- start() blocks until the callable finishes and returns its result
- Values passed to resume() arrive as new callable arguments
- Each Fiber runs on its own operating-system thread
- Fibers need Swoole or another extension to be installed