Introduction
While you rarely use Fibers directly, understanding practical patterns helps when working with async libraries and debugging fiber-based applications. This lesson covers schedulers, exception handling, and how async libraries use fibers under the hood.
Key Concepts
- Scheduler: A loop that manages multiple fibers, starting and resuming them in sequence.
- Exception Injection: Throwing an exception into a suspended fiber with
$fiber->throw(). - Async Sleep: A pattern where fibers cooperate with a timer system instead of blocking the entire process.
Real World Context
Every async PHP library (ReactPHP, Amp, Revolt) uses these patterns internally. When you write $response = await httpGet($url), the library suspends your fiber, registers an I/O callback, and resumes the fiber when data arrives. Understanding this mechanism helps you debug hangs, deadlocks, and unexpected behavior.
Deep Dive
Simple Scheduler
A scheduler runs multiple fibers cooperatively:
php<?php class SimpleScheduler { private array $fibers = []; public function add(callable $task): void { $this->fibers[] = new Fiber($task); } public function run(): void { foreach ($this->fibers as $fiber) { $fiber->start(); } while ($this->hasRunning()) { foreach ($this->fibers as $fiber) { if ($fiber->isSuspended()) { $fiber->resume(); } } } } private function hasRunning(): bool { foreach ($this->fibers as $fiber) { if (!$fiber->isTerminated()) { return true; } } return false; } }
The scheduler starts all fibers, then loops through them, resuming suspended ones until all complete.
Exception Handling
You can throw exceptions into a suspended fiber:
php<?php $fiber = new Fiber(function() { try { Fiber::suspend('waiting'); } catch (Exception $e) { echo "Caught: " . $e->getMessage(); return 'handled'; } }); $fiber->start(); // Throw exception into the fiber $fiber->throw(new Exception('Something went wrong')); $result = $fiber->getReturn(); echo $result; // 'handled'
The throw() method resumes the fiber, but the Fiber::suspend() call throws the given exception instead of returning normally. The fiber can catch and handle it.
How Async Libraries Use Fibers
Conceptually, async I/O works like this:
php<?php // What you write (high-level async API): function fetchData(): string { $response = await httpGet('https://api.example.com'); return $response->body; } // What the library does (simplified): function httpGet(string $url): Response { $fiber = Fiber::getCurrent(); // Register async I/O callback registerIoCallback($url, function($response) use ($fiber) { $fiber->resume($response); }); // Suspend until callback resumes us return Fiber::suspend(); }
The library suspends the fiber, registers an I/O callback with the event loop, and resumes the fiber when data arrives. The user code reads as synchronous.
When to Use Fibers Directly
Fibers are appropriate for:
- Building async frameworks — If you are creating an event loop or async runtime.
- Cooperative multitasking — Running multiple computations that voluntarily yield.
- Coroutine-like patterns — Generators on steroids with bidirectional communication.
For application code, use libraries built on fibers (Amp, ReactPHP) instead.
Common Pitfalls
- Resuming a terminated fiber — Calling
resume()orthrow()on a terminated fiber throws aFiberError. Always checkisTerminated()first. - Suspending outside a fiber — Calling
Fiber::suspend()from the main execution context throws aFiberError. Only call it from within a fiber callback.
Best Practices
- Check fiber state before operations — Always check
isSuspended()beforeresume()andisTerminated()before accessinggetReturn(). - Use structured concurrency — Track all spawned fibers and ensure they complete or are cancelled before the parent scope exits.
Summary
- A scheduler manages multiple fibers by starting and resuming them in a loop.
$fiber->throw()injects exceptions into suspended fibers.- Async libraries use fibers to make I/O non-blocking while keeping user code synchronous.
- Use async libraries (Amp, ReactPHP) for application code, not raw fibers.
- Always check fiber state before resume/throw operations.
Code Examples
<?php
class TaskRunner {
private array $tasks = [];
private array $results = [];
public function addTask(string $name, callable $task): self {
$this->tasks[$name] = new Fiber($task);
return $this;
}
public function run(): array {
foreach ($this->tasks as $name => $fiber) {
$fiber->start();
}
$pending = count($this->tasks);
while ($pending > 0) {
foreach ($this->tasks as $name => $fiber) {
if ($fiber->isSuspended()) {
$fiber->resume();
}
if ($fiber->isTerminated() && !isset($this->results[$name])) {
$this->results[$name] = $fiber->getReturn();
$pending--;
}
}
}
return $this->results;
}
}
$runner = new TaskRunner();
$runner->addTask('task1', function() {
for ($i = 0; $i < 3; $i++) { Fiber::suspend(); }
return 'Task 1 done';
});
$runner->addTask('task2', function() {
for ($i = 0; $i < 2; $i++) { Fiber::suspend(); }
return 'Task 2 done';
});
$results = $runner->run();
print_r($results);
// ['task1' => 'Task 1 done', 'task2' => 'Task 2 done']
?>