fast-forward / fork
A process manager for PHP that allows you to run multiple processes in parallel using the fork system call.
Requires
- php: ^8.3
- psr/log: ^3.0
Requires (Dev)
- fast-forward/dev-tools: dev-main
This package is auto-updated.
Last update: 2026-04-05 00:08:15 UTC
README
A PHP 8.3+ library for orchestrating forked workers with typed signals, immutable worker groups, captured worker output, and PSR-3 logging.
fast-forward/fork wraps pcntl_fork() and related POSIX primitives in a small, strongly-typed API
that is easier to reason about in real applications. It gives you a single manager for orchestration,
explicit worker objects for lifecycle inspection, immutable worker groups for batch coordination, and a
default signal handler that can shut everything down cleanly.
โจ Features
- ๐ PHP 8.3+ API with enums, readonly dependencies, and clear object boundaries.
- ๐งต
ForkManagerorchestration for spawning, waiting, and signaling workers. - ๐ท
Workerobjects with lifecycle state, exit code, termination signal, and captured output. - ๐ฆ Immutable
WorkerGroupcollections for batchwait()andkill()operations. - ๐ Typed
Signalenum for readable signal delivery and signal-aware exit status handling. - ๐ Pluggable
SignalHandlerInterfacewith a ready-to-useDefaultSignalHandler. - ๐ PSR-3 logger integration for worker lifecycle and streamed output events.
- ๐ก Partial output availability while workers are still running.
- ๐งช Ordered examples that go from basic usage to advanced coordination scenarios.
- โ ๏ธ Named library exceptions for invalid usage, logic violations, and runtime failures.
๐ฆ Installation
composer require fast-forward/fork
Runtime requirements
- PHP
^8.3 psr/log^3.0- A runtime that exposes the process-control functions checked by
ForkManager::isSupported() - In practice, this means a Unix-like CLI runtime with
pcntlandposixsupport enabled
The manager validates runtime support during construction. If the environment does not support
forking safely, it throws FastForward\Fork\Exception\RuntimeException.
๐ ๏ธ Usage
Basic usage
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Worker\WorkerInterface; $manager = new ForkManager(); $group = $manager->fork( static function (WorkerInterface $worker): int { echo sprintf("worker %d started\n", $worker->getPid()); usleep(150_000); echo sprintf("worker %d finished\n", $worker->getPid()); return 0; }, 3, ); $group->wait(); foreach ($group as $worker) { printf( "pid=%d exit=%s signal=%s\n", $worker->getPid(), var_export($worker->getExitCode(), true), $worker->getTerminationSignal()?->name ?? 'none', ); }
Wait for everything managed by the same manager
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Worker\WorkerInterface; $manager = new ForkManager(); $apiWorkers = $manager->fork( static fn (WorkerInterface $worker): int => 0, 2, ); $queueWorkers = $manager->fork( static fn (WorkerInterface $worker): int => 0, 2, ); // Waits for every worker created by this manager. $manager->wait();
Read worker output before completion
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Worker\WorkerInterface; $manager = new ForkManager(); $group = $manager->fork( static function (WorkerInterface $worker): int { echo sprintf("worker %d step 1\n", $worker->getPid()); usleep(250_000); echo sprintf("worker %d step 2\n", $worker->getPid()); return 0; }, 2, ); foreach ($group->all() as $worker) { echo $worker->getOutput(); } $group->wait();
Stop workers explicitly
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Signal\Signal; use FastForward\Fork\Worker\WorkerInterface; $manager = new ForkManager(); $group = $manager->fork( static function (WorkerInterface $worker): never { while (true) { echo sprintf("worker %d heartbeat\n", $worker->getPid()); usleep(100_000); } }, 2, ); $group->kill(Signal::Terminate); $group->wait();
Use the default signal handler
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Signal\DefaultSignalHandler; use FastForward\Fork\Signal\Signal; $manager = new ForkManager( signalHandler: new DefaultSignalHandler(exitOnSignal: false), ); // Later, in the master process: posix_kill($manager->getMasterPid(), Signal::Terminate->value);
๐งฐ API Summary
Core classes
| Class | Responsibility | Highlights |
|---|---|---|
FastForward\Fork\Manager\ForkManager |
Master orchestration | fork(), wait(), kill(), getMasterPid() |
FastForward\Fork\Worker\Worker |
One forked worker | PID, exit code, termination signal, stdout, stderr |
FastForward\Fork\Worker\WorkerGroup |
Immutable worker collection | all(), get(pid), getRunning(), getStopped(), wait(), kill() |
FastForward\Fork\Signal\Signal |
Typed POSIX signal enum | Signal::Terminate, Signal::Kill, Signal::Interrupt, exitStatus() |
FastForward\Fork\Signal\DefaultSignalHandler |
Ready-made signal propagation strategy | Graceful propagation, optional wait, escalation support |
Main methods
| Target | Method | Description |
|---|---|---|
ForkManager |
fork(callable $callback, int $workerCount = 1) |
Spawn N workers for the same callback and return them as a group |
ForkManager |
wait(WorkerInterface|WorkerGroupInterface ...$workers) |
Wait for targeted workers or every worker managed by the manager |
ForkManager |
kill(Signal $signal = Signal::Terminate, WorkerInterface|WorkerGroupInterface ...$workers) |
Send a signal to targeted workers or all managed workers |
Worker |
wait() |
Wait for a single worker |
Worker |
kill() |
Signal a single worker |
Worker |
getOutput() / getErrorOutput() |
Read captured output, including partial output while still running |
WorkerGroup |
wait() |
Wait for every worker in the group |
WorkerGroup |
kill() |
Signal every worker in the group |
WorkerGroup |
getRunning() / getStopped() |
Inspect current group state |
Exceptions
| Exception | Use case |
|---|---|
FastForward\Fork\Exception\InvalidArgumentException |
Invalid worker count, foreign worker, foreign worker group |
FastForward\Fork\Exception\LogicException |
Invalid control-flow usage, such as forking from a worker using the same manager |
FastForward\Fork\Exception\RuntimeException |
Unsupported runtime, fork failure, wait failure, transport allocation failure |
FastForward\Fork\Exception\ForkExceptionInterface |
Catch-all contract for library-specific exceptions |
๐ Integration
This library is intentionally small and integrates cleanly with CLI-oriented PHP applications:
PSR-3: inject anyPsr\Log\LoggerInterfaceintoForkManagerto trace worker lifecycle and streamed output- POSIX signals: use the typed
Signalenum instead of raw integers throughout your own code - Custom shutdown logic: implement
FastForward\Fork\Signal\SignalHandlerInterfaceand inject it into the manager - Fast Forward tooling: the repository already includes ordered examples and GitHub workflows that fit the rest of the ecosystem
This package is best suited for:
- queue consumers
- parallel CLI jobs
- daemons and long-running processes
- controlled fan-out tasks where you want explicit lifecycle ownership
๐ Directory Structure
src/
โโโ Exception/
โ โโโ ForkExceptionInterface.php
โ โโโ InvalidArgumentException.php
โ โโโ LogicException.php
โ โโโ RuntimeException.php
โโโ Manager/
โ โโโ ForkManager.php
โ โโโ ForkManagerInterface.php
โโโ Signal/
โ โโโ DefaultSignalHandler.php
โ โโโ Signal.php
โ โโโ SignalHandlerInterface.php
โโโ Worker/
โโโ Worker.php
โโโ WorkerGroup.php
โโโ WorkerGroupInterface.php
โโโ WorkerInterface.php
โโโ WorkerOutputTransport.php
โโโ WorkerState.php
examples/
โโโ 01-basic-fork.php
โโโ 02-inspect-worker-group.php
โโโ 03-manager-wait-all.php
โโโ 04-stream-worker-output.php
โโโ 05-capture-worker-errors.php
โโโ 06-group-kill.php
โโโ 07-targeted-manager-control.php
โโโ 08-logger-integration.php
โโโ 09-default-signal-handler.php
โโโ 10-verify-library-behavior.php
โโโ bootstrap.php
โโโ Support/
โ๏ธ Advanced and Customization
Custom signal handling
You can replace the default shutdown strategy entirely by implementing
FastForward\Fork\Signal\SignalHandlerInterface and injecting it into the manager:
<?php declare(strict_types=1); use FastForward\Fork\Manager\ForkManager; use FastForward\Fork\Manager\ForkManagerInterface; use FastForward\Fork\Signal\Signal; use FastForward\Fork\Signal\SignalHandlerInterface; final class GracefulReloadHandler implements SignalHandlerInterface { public function signals(): array { return [Signal::User1]; } public function __invoke(ForkManagerInterface $manager, Signal $signal): void { $manager->kill(Signal::Terminate); $manager->wait(); } } $manager = new ForkManager(signalHandler: new GracefulReloadHandler());
Output capture model
The library transfers worker output through socket pairs so the master process can inspect partial or final output while workers run.
What is captured:
echo,print,printf, and other output-buffered userland output- warnings routed through the worker error handler
- exceptions caught by the worker wrapper
What is not intercepted automatically:
- direct writes to native file descriptors such as
fwrite(STDOUT, ...) - direct writes to
STDERRthat bypass the worker wrapper
If you need descriptor-level capture, you will need explicit descriptor redirection in your own worker code.
Nested process trees
The same ForkManager instance cannot be reused from inside a worker process. If a worker needs to create
its own child workers, instantiate a new manager inside that worker process.
๐ ๏ธ Versioning and Upgrade Notes
- The Composer branch alias currently exposes the development line as
1.x-dev - The public API is already split into focused namespaces:
Manager,Worker,Signal, andException - Before adopting new development versions, review release notes and examples because process orchestration libraries are sensitive to runtime and API details
โ FAQ
Does this package emulate workers when pcntl or posix are missing?
No. The manager fails explicitly with RuntimeException when the runtime is not supported.
Can I read worker output before wait() finishes?
Yes. Worker::getOutput() and Worker::getErrorOutput() can expose partial output while the worker is still running.
Can I wait for everything without tracking each group manually?
Yes. Calling $manager->wait() with no arguments waits for every worker created by that manager.
Can I kill only some workers?
Yes. You can target individual Worker instances, entire WorkerGroup instances, or mix both in a single manager call.
Can a worker reuse the parent manager to create nested workers?
No. A worker must create a new manager if it needs its own process tree.
Does this library work in web requests?
It is designed for CLI-like runtimes with POSIX process control available. Inference from the required functions:
it is generally a poor fit for standard web SAPIs and unsupported on environments without pcntl/posix.
๐ Comparison
| Capability | fast-forward/fork |
Manual pcntl_* orchestration |
|---|---|---|
| Typed signals via enum | โ | โ |
| Immutable worker groups | โ | โ |
| Worker objects with state inspection | โ | โ |
| Partial output capture | โ | โ |
| PSR-3 logger integration | โ | โ |
| Default signal propagation strategy | โ | โ |
| Named library exceptions | โ | โ |
| Ordered learning examples | โ | โ |
๐งช Examples
The repository includes a numbered progression of examples:
- examples/01-basic-fork.php
- examples/02-inspect-worker-group.php
- examples/03-manager-wait-all.php
- examples/04-stream-worker-output.php
- examples/05-capture-worker-errors.php
- examples/06-group-kill.php
- examples/07-targeted-manager-control.php
- examples/08-logger-integration.php
- examples/09-default-signal-handler.php
- examples/10-verify-library-behavior.php
- examples/README.md
๐ก License
MIT ยฉ 2026 Felipe Sayรฃo Lobato Abreu
See LICENSE for details.
๐ค Contributing
Issues, ideas, and pull requests are welcome.
Recommended local checks:
find src examples -name '*.php' -print0 | xargs -0 -n1 php -l vendor/bin/phpstan analyse src examples --no-progress --debug php examples/10-verify-library-behavior.php
If you use the Fast Forward dev tooling in this repository, you can also run:
composer dev-tools composer dev-tools:fix
๐ Links
- Repository
- Packagist
- Issues
- Examples
- PHP Process Control (
pcntl) - PHP POSIX Functions
- PSR-3 Logger Interface
- RFC 2119
Keywords for discoverability: PHP worker pool, pcntl_fork wrapper, POSIX signal handling, process manager,
parallel CLI jobs, worker orchestration, PSR-3 logging, Fast Forward PHP.