PHPackages                             smwks/superprocess - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. smwks/superprocess

ActiveLibrary[CLI &amp; Console](/categories/cli)

smwks/superprocess
==================

A fluent PHP library for supervised master-child process control using pcntl and pipes

v0.1.1(2mo ago)0381MITPHPPHP ^8.4.0CI passing

Since Feb 23Pushed 2mo agoCompare

[ Source](https://github.com/smwks/superprocess)[ Packagist](https://packagist.org/packages/smwks/superprocess)[ GitHub Sponsors](https://github.com/ralphschindler)[ RSS](/packages/smwks-superprocess/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (7)Versions (3)Used By (1)

SuperProcess
============

[](#superprocess)

```
+--------------------------------+
|                                |
|  >> SuperProcess               |
|                                |
|  [master] --+--> [worker]      |
|             +--> [worker]      |
|             +--> [worker]      |
|                                |
|  fork · supervise · scale      |
+--------------------------------+
```

A fluent PHP library for supervised master-child process control using `pcntl` and pipes. Run one or more copies of a command or PHP closure, keep them alive automatically, communicate with them via stdin/stdout and a structured IPC channel, and scale the pool up or down at runtime.

Two features define SuperProcess:

- **Closures fork.** Pass `closure()` any PHP closure and it becomes an independent OS process — no serialisation boilerplate, no separate file. The closure runs in a `pcntl_fork()` copy of the master, with a socket wired back for structured IPC.
- **The heartbeat drives the pool.** Register a periodic callback that fires on the master process. Inside it you have full control: query a database, read a queue depth, inspect a config value — then call `scaleUp()` or `scaleDown()` to match worker supply to real-time demand.

> **Requires PHP 8.4+, `ext-pcntl`, `ext-posix` — Linux and macOS only.**

---

Installation
------------

[](#installation)

```
composer require smwks/superprocess
```

---

Quick start
-----------

[](#quick-start)

**Supervise a pool of command workers:**

```
use SMWks\SuperProcess\Child;
use SMWks\SuperProcess\CreateReason;
use SMWks\SuperProcess\ExitReason;
use SMWks\SuperProcess\SuperProcess;

$sp = new SuperProcess;
$sp->command('php artisan inspire:loop')
   ->scaleLimits(min: 2, max: 5)
   ->onChildCreate(fn (Child $c, CreateReason $r) => printf("[master] spawned %d\n", $c->pid))
   ->onChildExit(fn (Child $c, ExitReason $r)    => printf("[master] %d exited\n",   $c->pid))
   ->onChildOutput(fn (Child $c, string $data)   => print $data)
   ->run(); // blocks until SIGTERM is received
```

**Fork a closure as a worker process:**

```
use SMWks\SuperProcess\Child;
use SMWks\SuperProcess\SuperProcess;

// The closure is forked by the master — no separate file needed.
$sp = new SuperProcess;
$sp->closure(function (mixed $socket): void {
        for ($i = 1; $i  $i]) . "\n");
            sleep(1);
        }
    })
    ->scaleLimits(min: 3, max: 3)
    ->onChildMessage(fn (Child $c, mixed $msg) => printf("[%d] step %d\n", $c->pid, $msg['step']))
    ->run();
```

**Drive pool size from an external source via heartbeat:**

```
use SMWks\SuperProcess\SuperProcess;

// Every 5 seconds the master checks the queue depth and adjusts the pool.
$sp = new SuperProcess;
$sp->command('php artisan queue:work')
   ->scaleLimits(min: 1, max: 20)
   ->heartbeat(5, function (SuperProcess $sp): void {
       $depth = (int) DB::scalar('SELECT COUNT(*) FROM jobs WHERE queue = ?', ['default']);

       if ($depth > 100) { $sp->scaleUp();   } // no-op if already at max
       if ($depth < 10)  { $sp->scaleDown(); } // no-op if already at min
   })
   ->run();
```

---

Concepts
--------

[](#concepts)

### Command children

[](#command-children)

`command(string $cmd)` runs an external process for each child slot. The command is started with `proc_open()`, so it inherits `PATH` and environment variables. Each child gets four file descriptors:

fddirectionpurpose0 — stdinmaster → child`sendChildInput()`1 — stdoutchild → master`onChildOutput()`2 — stderrchild → master`onChildOutput()`3 — IPCchild → master`onChildMessage()` (JSON lines)All pipes are non-blocking; reads happen inside the event loop via `stream_select()`.

### Closure children

[](#closure-children)

`closure(Closure $fn)` forks the master with `pcntl_fork()` and runs the closure in the child. The closure receives a socket resource as its only argument — write JSON lines to it to send structured messages to the master via `onChildMessage()`.

```
$sp->closure(function (mixed $socket): void {
    for ($i = 1; $i  $i * 20]) . "\n");
        sleep(1);
    }
});
```

The closure captures its surrounding scope at the moment `run()` is called — that is when the fork actually happens. Any variable in scope at that point is available inside the child. Because the fork copies the master's entire memory image, expensive one-time work (bootstrapping a framework, parsing config, establishing a schema) is paid once in the master and shared copy-on-write across every worker.

### Child lifecycle

[](#child-lifecycle)

On startup `run()` spawns `min` children. When a child exits:

1. `onChildExit` fires with an `ExitReason`.
2. If running count drops below `min`, a replacement is spawned with `CreateReason::Replacement`.

The master never exits the event loop on its own — send it `SIGTERM` or `SIGINT` (or call `signal(posix_getpid(), ProcessSignal::Stop)` from within a callback) to trigger a graceful shutdown.

### Heartbeat

[](#heartbeat)

`heartbeat(int $intervalSeconds, Closure $fn)` registers a callback that fires on the master process at a regular interval throughout the event loop. The callback receives the live `SuperProcess` instance, giving it full runtime control.

This is the primary mechanism for **externally-driven pool management**. Instead of sizing the pool statically at startup, query whatever source of truth you have — a queue depth, a database flag, a config value — and nudge the pool toward the right size:

```
$sp->command('php artisan queue:work')
   ->scaleLimits(min: 1, max: 20)
   ->heartbeat(5, function (SuperProcess $sp): void {
       $depth = (int) DB::scalar('SELECT COUNT(*) FROM jobs WHERE queue = ?', ['default']);

       if ($depth > 100) { $sp->scaleUp();   } // no-op if already at max
       if ($depth < 10)  { $sp->scaleDown(); } // no-op if already at min
   })
   ->run();
```

Because the heartbeat runs on the master — the process that owns the event loop — it is single-threaded and safe to call without locks. `scaleUp()` and `scaleDown()` each step by one and are no-ops when the pool is already at the configured limit.

### Graceful shutdown

[](#graceful-shutdown)

On `SIGTERM` or `SIGINT` (Ctrl+C) the master:

1. Runs the `onShutdown` callback, if registered, while all children are still alive.
2. Sends `SIGTERM` to every child.
3. Waits up to 5 seconds for each to exit.
4. Sends `SIGKILL` to any that remain.

The `onShutdown` callback is the right place to flush state, close connections, or send a final message to children before they are signalled:

```
$sp->onShutdown(function (SuperProcess $sp): void {
    echo "Shutting down — waiting for workers to finish current jobs\n";
})
->run();
```

---

API reference
-------------

[](#api-reference)

### Configuration

[](#configuration)

```
// Set the command to run in each child (mutually exclusive with closure())
->command(string $command): static

// Set a PHP closure to run in each child (mutually exclusive with command())
->closure(Closure $fn): static   // fn(resource $socket): void

// Set the min/max number of running children (default: 1, 1)
->scaleLimits(int $min, int $max): static

// Register a periodic master heartbeat
->heartbeat(int $intervalSeconds, Closure $fn): static  // fn(SuperProcess $self): void
```

### Callbacks

[](#callbacks)

```
// Called when a child is spawned
->onChildCreate(Closure $fn): static   // fn(Child $child, CreateReason $reason): void

// Called when a child exits
->onChildExit(Closure $fn): static     // fn(Child $child, ExitReason $reason): void

// Called when SIGUSR1 or SIGUSR2 is received by the master
->onChildSignal(Closure $fn): static   // fn(Child $child, int $signal): void

// Called for each JSON message received on the child's IPC channel
->onChildMessage(Closure $fn): static  // fn(Child $child, mixed $message): void

// Called with raw stdout/stderr data from a command child
->onChildOutput(Closure $fn): static   // fn(Child $child, string $data): void

// Called once on shutdown (SIGTERM or SIGINT), before children are signalled
->onShutdown(Closure $fn): static      // fn(SuperProcess $self): void
```

### Runtime control

[](#runtime-control)

```
// Write to a running child's stdin
->sendChildInput(int $pid, string $data): void

// Send any POSIX signal to a PID (use ProcessSignal constants)
->signal(string|int $pid, ProcessSignal $signal): void

// Spawn one more child (if below max)
->scaleUp(): static

// Terminate one child (if above min)
->scaleDown(): static

// Start the blocking event loop
->run(): void
```

### Enums and constants

[](#enums-and-constants)

```
// Why a child was created
CreateReason::Initial       // first spawn on run()
CreateReason::Replacement   // auto-restarted after exit
CreateReason::ScaleUp       // spawned by scaleUp()

// Why a child exited
ExitReason::Normal          // exited via exit() / end of script
ExitReason::Signal          // terminated by a signal (SIGTERM etc.)
ExitReason::Killed          // force-killed with SIGKILL
ExitReason::Unknown         // status could not be determined

// Signal shortcuts (values map to POSIX signal numbers)
ProcessSignal::Stop         // SIGTERM — graceful stop
ProcessSignal::Kill         // SIGKILL — force kill
ProcessSignal::Reload       // SIGHUP  — reload
ProcessSignal::Usr1         // SIGUSR1
ProcessSignal::Usr2         // SIGUSR2
```

### `Child` properties

[](#child-properties)

```
$child->pid            // int  — process ID
$child->createReason   // CreateReason
$child->running        // bool — false once the process has exited
$child->exitCode       // int  — exit code (populated after exit)
$child->exitReason     // ExitReason (populated after exit)
```

---

Sending structured messages from a command child
------------------------------------------------

[](#sending-structured-messages-from-a-command-child)

Write newline-delimited JSON to file descriptor 3. The master delivers each parsed line to `onChildMessage`.

```
// child-worker.php
$ipc = fopen('php://fd/3', 'w');

fwrite($ipc, json_encode(['type' => 'started', 'pid' => getmypid()]) . "\n");

// ... do work ...

fwrite($ipc, json_encode(['type' => 'done', 'items_processed' => 1234]) . "\n");
fclose($ipc);
```

```
// supervisor
$sp->command('php child-worker.php')
   ->onChildMessage(function (Child $child, mixed $msg): void {
       echo "[{$child->pid}] {$msg['type']}\n";
   });
```

---

Signals
-------

[](#signals)

Signal received by masterBehaviour`SIGTERM`Graceful shutdown — fires `onShutdown`, then drains children`SIGINT`Graceful shutdown — same as `SIGTERM` (handles Ctrl+C)`SIGHUP`Forwarded to all children`SIGCHLD`Internal — triggers zombie reaping and pool replenishment`SIGUSR1`Fires `onChildSignal` for every running child`SIGUSR2`Fires `onChildSignal` for every running child---

Development
-----------

[](#development)

```
composer lint          # fix code style with Pint
composer refactor      # apply Rector suggestions
composer test:lint     # check code style
composer test:types    # PHPStan (max level)
composer test:unit     # Pest unit tests
composer test          # run all checks
```

---

**SuperProcess** is open-sourced under the [MIT license](LICENSE.md).

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance84

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

84d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9c4a15cb4e97bc39a3aea8304fb04d56c0d753f2a6b79c83db0a894647ba8de7?d=identicon)[ralphschindler](/maintainers/ralphschindler)

---

Top Contributors

[![ralphschindler](https://avatars.githubusercontent.com/u/76674?v=4)](https://github.com/ralphschindler "ralphschindler (12 commits)")

---

Tags

phpprocessforksupervisorpcntlipc

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/smwks-superprocess/health.svg)

```
[![Health](https://phpackages.com/badges/smwks-superprocess/health.svg)](https://phpackages.com/packages/smwks-superprocess)
```

###  Alternatives

[misterion/ko-process

Simple pcntl fork wrapper and process manager

177337.7k6](/packages/misterion-ko-process)[duncan3dc/fork-helper

Simple class to fork processes in PHP and allow multi-threading

73548.0k4](/packages/duncan3dc-fork-helper)[arara/process

Provides a better API to work with processes on Unix-like systems

16861.7k2](/packages/arara-process)[misterion/ko-worker

Ko-worker project is a base to develop amqp based message dispatchers

2713.4k](/packages/misterion-ko-worker)[zhgzhg/gphpthread

Generic PHP Threads library using only pure PHP

154.1k](/packages/zhgzhg-gphpthread)[lifo/php-ipc

Simple PHP Inter Process Communication (IPC) library

285.5k](/packages/lifo-php-ipc)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
