sanmai / round-robin
Minimal round-robin scheduler for PHP Fibers
Fund package maintenance!
sanmai
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/sanmai/round-robin
Requires
- php: >=8.2
Requires (Dev)
- ergebnis/composer-normalize: ^2.8
- esi/phpunit-coverage-check: >2
- friendsofphp/php-cs-fixer: ^3.17
- infection/infection: >=0.32.3
- league/pipeline: ^0.3 || ^1.0
- php-coveralls/php-coveralls: ^2.4.1
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2
- phpunit/phpunit: >=9.4 <12
- sanmai/phpstan-rules: ^0.3.11
- sanmai/phpunit-double-colon-syntax: ^0.1.1
This package is auto-updated.
Last update: 2026-01-19 08:19:19 UTC
README
A minimal, dependency-free round-robin scheduler for PHP Fibers.
A thin layer for cooperative multitasking:
- no event loop
- no promises
- no futures
- no async/await syntax
- no I/O magic
You write synchronous-looking code, insert Fiber::suspend() where it makes sense,
and the scheduler resumes fibers in round-robin order.
Installation
composer require sanmai/round-robin
Design goals
- Explicit control flow - fibers yield only where you say so
- Deterministic scheduling - simple round-robin, no heuristics
- Shared memory model - no message passing abstraction
- Zero dependencies
- Tiny surface area - easy to read, audit, and modify
Non-goals:
- Non-blocking I/O
- Parallelism
- Promises / futures
- Structured concurrency
- Cancellation graphs
If you need those, use Amp or Revolt instead.
Quality assurance
This library maintains 100% code coverage and 100% mutation score with Infection. Tests cover every line and every mutation.
Basic usage
Creating a scheduler
use RoundRobin\Scheduler; $scheduler = new Scheduler();
Adding fibers
$scheduler->add(new Fiber(function (): void { echo "task A: step 1\n"; Fiber::suspend(); 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"; }));
Running
$scheduler->run();
Output:
task A: step 1
task B: step 1
task A: step 2
task B: step 2
Passing data between fibers
Fibers share memory. Use ordinary PHP data structures.
$queue = new SplQueue(); $done = false; $producer = new Fiber(function () use ($queue, &$done): void { foreach ([1, 2, 3] as $value) { $queue->enqueue($value); Fiber::suspend(); } $done = true; }); $consumer = new Fiber(function () use ($queue, &$done): void { while (!$done || !$queue->isEmpty()) { if ($queue->isEmpty()) { Fiber::suspend(); continue; } echo "got {$queue->dequeue()}\n"; Fiber::suspend(); } }); $scheduler->add($producer); $scheduler->add($consumer); $scheduler->run();
API
RoundRobin\Scheduler
add(Fiber $fiber): void
Adds a fiber to the scheduler.
- Fibers may be added before or after
run() - A fiber is scheduled until it terminates
run(): void
Runs all scheduled fibers until all of them terminate.
Behavior:
- starts fibers that have not yet started
- resumes suspended fibers
- skips terminated fibers
- stops when no runnable fibers remain
tick(): bool
Runs a single round of scheduling.
Returns:
trueif at least one fiber was executedfalseif all fibers are terminated
Useful for embedding the scheduler into an existing loop.
How scheduling works
- Only one fiber runs at a time
- Fibers must call
Fiber::suspend()to yield - The scheduler resumes fibers in FIFO order
- There is no preemption
- Blocking I/O blocks the entire process
This is cooperative multitasking by design.
About I/O
This library does not make I/O non-blocking.
If a fiber performs blocking I/O:
- the entire PHP process blocks
- no other fiber runs
If you need:
- non-blocking file or socket I/O
- readiness notifications
- timers
Use an event loop (e.g. Revolt) or integrate stream_select() yourself.
When to use this
Use this when you need:
- incremental Fiber adoption
- structured long-running logic
- explicit yield points
- deterministic execution
- no framework lock-in
Skip if you need:
- async/await semantics
- transparent non-blocking I/O
- parallelism
- cancellation propagation
License
Apache-2.0