jenner/simple_fork
SimpleFork is a PHP PCNTL-based multi-process framework with Java-like Thread/Runnable APIs. It provides process pools, automatic zombie recovery, signal handling, and IPC options like shared memory, SysV queues/semaphores, file locks, and Redis queues/cache.
Install the package:
composer require jenner/simple_fork
Ensure ext-pcntl is enabled in php.ini.
First use case: Run a one-off process
use Jenner\SimpleFork\Process;
$process = new Process(function() {
echo "Hello from child process!\n";
});
$process->start();
$process->wait(); // Block until completion
Key files to explore:
src/Process.php: Core process class (extend or use with callbacks).src/Pool.php: Basic process pool (for heterogeneous tasks).src/FixedPool.php: Fixed-size worker pool (for homogeneous tasks).src/Cache/, src/Queue/, src/Lock/: IPC/sync implementations.$process = new Process(function() {
// Task logic here
});
$process->start();
Use when: One-off tasks or simple scripts.
Process (Reusable Workers)class ImageProcessor extends Process {
protected $imagePath;
public function __construct(string $imagePath) {
$this->imagePath = $imagePath;
}
public function run() {
// Process image logic
file_put_contents("processed_{$this->imagePath}", $this->processImage());
}
}
Use when: Reusable workers with state (e.g., config, dependencies).
Runnable (Interface-Based)class RunnableTask implements Runnable {
public function run() {
// Task logic
}
}
$process = new Process(new RunnableTask());
Use when: Dependency injection or testing (easier to mock).
$pool = new Pool();
$pool->execute(new Process(new RunnableTask()));
$pool->execute(new Process(new AnotherTask()));
$pool->wait(); // Blocks until all processes finish
Use when: Heterogeneous tasks (e.g., mixed batch jobs).
$pool = new FixedPool(4); // Max 4 concurrent workers
$pool->execute(new Process(new RunnableTask()));
// ... add more tasks
$pool->wait();
Use when: CPU-bound tasks with resource limits (e.g., API rate limits).
$pool = new SinglePool();
$pool->execute(new Process(new RunnableTask()));
// Next task starts only after previous finishes
Use when: Sequential dependency (e.g., pipeline stages).
$pool = new ParallelPool(new RunnableTask(), 3); // 3 workers
$pool->start();
$pool->keep(true); // Run indefinitely (e.g., for consumers)
Use when: Long-running services (e.g., Kafka consumers, WebSocket handlers).
$cache = new \Jenner\SimpleFork\Cache\SharedMemory();
$cache->set('key', 'value'); // Parent
// ...
$value = $cache->get('key'); // Child
Use when: High-speed data sharing between processes (e.g., caching).
$cache = new \Jenner\SimpleFork\Cache\RedisCache();
$cache->set('key', 'value');
Use when: Cross-machine coordination or persistence.
$queue = new \Jenner\SimpleFork\Queue\RedisQueue();
$queue->put('task_data'); // Producer
// ...
$data = $queue->get(); // Consumer
Use when: Decoupled producers/consumers (e.g., background jobs).
$sem = \Jenner\SimpleFork\Lock\Semaphore::create('lock_key');
$sem->acquire(); // Block until available
// Critical section
$sem->release();
Use when: Mutual exclusion (e.g., file locks, rate limiting).
use Jenner\SimpleFork\Process;
use Illuminate\Console\Command;
class ProcessCommand extends Command {
public function handle() {
$process = new Process(function() {
// Heavy task
});
$process->start();
$this->info("Process started (PID: {$process->getPid()})");
}
}
Use when: CLI-driven parallel tasks (e.g., php artisan process:images).
// config/queue.php
'connections' => [
'simple_fork' => [
'driver' => 'simple_fork',
'pool' => FixedPool::class,
'size' => 5,
],
],
Use when: Replacing Laravel’s queue system for CPU-bound jobs.
public function register() {
$this->app->singleton('process.pool', function() {
return new FixedPool(4);
});
}
Use when: Centralized process management (e.g., for microservices).
// In master process
pcntl_signal(SIGTERM, function() {
$pool->shutdown(); // Graceful exit
});
// In child process
Process::registerSignalHandler(SIGTERM, function() {
// Cleanup
});
Use when: Handling SIGTERM/SIGINT for graceful shutdowns.
Zombie Processes
wait() can leave orphaned processes.wait() or use Pool::shutdown().ps aux | grep php for stragglers.Signal Handling
Process::registerSignalHandler() before start().declare(ticks=1)—use pcntl_signal_dispatch() instead.Shared Resources
new Redis() per process).RedisCluster or connection pooling for high-throughput.IPC Limitations
sysvshm) is local-only (no cross-server).sysvmsg) have size limits (~8KB per message).FixedPool Reload
reload() kills old processes abruptly.run() methods.PHP Version
declare(strict_types=1) and polyfills if needed.Log Process IDs
echo "Master PID: " . getmypid() . "\n";
$process->start();
echo "Child PID: " . $process->getPid() . "\n";
Check Exit Codes
$process->wait();
if ($process->getExitCode() !== 0) {
throw new \RuntimeException("Process failed");
}
Use strace for Hangs
strace -p <PID> # Debug blocked processes
Monitor with htop
htop -p $(pgrep php) # Watch process resource usage
Custom IPC
CacheInterface, QueueInterface, or LockInterface for new backends (e.g., database locks).Process Lifecycle Hooks
class MyProcess extends Process {
public function onStart() { /* Pre-run */ }
public function onExit() { /* Post-run */ }
}
Dynamic Pool Sizing
$pool = new FixedPool(4);
// Later...
$pool->setSize(8); // Resize (requires reload)
Error Handling
$process->onError(function($exitCode, $signal) {
// Log or retry
});
Overhead of fork()
How can I help you explore Laravel packages today?