PHP Fibers — Internals & Scheduler Patterns
Also Known As
TL;DR
Explanation
A PHP Fiber is a stackful coroutine — it gets its own call stack (unlike generators which resume from a single yield point). Internally, the Zend Engine allocates a separate C stack for each fiber (configurable via `fiber.stack_size` in php.ini, default 2MB) and saves/restores CPU registers and the stack pointer on suspend/resume. `Fiber::suspend($value)` saves the current stack frame, returns control to the caller, and passes `$value` out. `$fiber->resume($value)` restores the stack frame and passes `$value` back in as the return value of `Fiber::suspend()`. Unlike threads, fibers are cooperative — only one fiber runs at a time, switching only at explicit `Fiber::suspend()` calls, so no mutexes are needed for shared state. A scheduler is a loop that tracks pending fibers and resumes them when their awaited condition is met (I/O ready, timer elapsed). Libraries like ReactPHP and Revolt PHP use fibers to implement `async/await`-style concurrency on top of an event loop.
Diagram
sequenceDiagram
participant S as Scheduler
participant FA as Fiber A
participant FB as Fiber B
S->>FA: start()
FA->>FA: step 1
FA->>S: Fiber::suspend()
S->>FB: start()
FB->>FB: step 1
FB->>S: Fiber::suspend()
S->>FA: resume()
FA->>FA: step 2
FA->>S: terminated
S->>FB: resume()
FB->>FB: step 2
FB->>S: terminated
Common Misconception
Why It Matters
Common Mistakes
- Not handling exceptions thrown into a fiber — `$fiber->throw(new Exception)` resumes the fiber with an exception at the suspend point; uncaught, it propagates to the caller.
- Blocking inside a fiber (sleep(), PDO query without async driver) — blocks the entire PHP process, defeating the purpose of cooperative concurrency.
- Creating thousands of fibers simultaneously — each fiber allocates a full stack (default 2MB); 1000 fibers = 2GB RAM. Pool and reuse fibers for high-concurrency workloads.
- Calling `Fiber::suspend()` outside a fiber — throws a `FiberError`; always check `Fiber::getCurrent() !== null`.
Code Examples
// Blocking inside a fiber — defeats cooperative concurrency:
$fiber = new Fiber(function(): void {
sleep(2); // blocks entire process — no other fiber runs
$result = file_get_contents('https://example.com'); // blocking I/O
Fiber::suspend($result);
});
$fiber->start();
// Simple cooperative scheduler using Fibers + non-blocking I/O:
class Scheduler {
private array $fibers = [];
public function add(Fiber $fiber): void {
$this->fibers[] = $fiber;
}
public function run(): void {
while ($this->fibers) {
foreach ($this->fibers as $key => $fiber) {
if (!$fiber->isStarted()) $fiber->start();
elseif ($fiber->isSuspended()) $fiber->resume();
if ($fiber->isTerminated()) {
unset($this->fibers[$key]);
}
}
}
}
}
$scheduler = new Scheduler();
$scheduler->add(new Fiber(function(): void {
echo "Task A: step 1\n";
Fiber::suspend(); // yield control
echo "Task A: step 2\n";
}));
$scheduler->add(new Fiber(function(): void {
echo "Task B: step 1\n";
Fiber::suspend();
echo "Task B: step 2\n";
}));
$scheduler->run();
// Output: Task A: step 1 / Task B: step 1 / Task A: step 2 / Task B: step 2