Runs external processes (ImageMagick, wkhtmltopdf, git, ffmpeg, your own
scripts) under a rasuvaeff/context
budget. When the deadline passes or the context is cancelled, the process is
really stopped — SIGTERM, then SIGKILL after a grace period — instead of
running on after the caller gave up.
Using an AI coding assistant? llms.txt contains a compact API reference you can share with the model. Projects using the llm/skills Composer plugin also get this package's agent skill synced into
.agents/skills/automatically on install.
- PHP 8.3+
rasuvaeff/context^0.3symfony/process^7.0 or ^8.0
composer require rasuvaeff/context-process- Only the process itself gets the signal. Pass the command as an argv
array (
new Process(['convert', $in, $out])), not a shell string: withProcess::fromShellCommandline()the shell receives SIGTERM and its children may keep running. - The grace period is not bounded by the budget: a process that handles
SIGTERM gets
$gracePeriodseconds to clean up even after the deadline. - Termination is noticed within one poll interval (
$pollInterval, 50 ms by default); a cancellation requested from a signal handler wakes the wait immediately. - Windows: there are no signals — symfony/process terminates the process
(
taskkill) instead of SIGTERM/SIGKILL.
use Rasuvaeff\Context\ContextExpiredException;
use Rasuvaeff\Context\Process\ContextProcess;
use Symfony\Component\Process\Process;
try {
$exitCode = ContextProcess::run(
new Process(['convert', $in, '-resize', '50%', $out]),
$context, // e.g. the request or job context
callback: static fn(string $type, string $chunk) => $log->debug($chunk),
gracePeriod: 2.0, // SIGTERM → wait → SIGKILL
);
} catch (ContextExpiredException) {
// the budget ran out: the process is already stopped
}| Situation | What happens |
|---|---|
| The process finishes in time | Its exit code is returned |
| The deadline passes | The process is stopped (SIGTERM, SIGKILL after gracePeriod); ContextExpiredException |
The context is cancelled (also via requestCancel() from a signal handler, e.g. rasuvaeff/context-cli) |
The process is stopped; ContextCancelledException with the reason as previous |
| The context is already terminated | The process is never started |
The process's own setTimeout() / setIdleTimeout() fires |
symfony/process stops it; its ProcessTimedOutException is rethrown unchanged |
| The callback throws | The process is stopped; the callback's exception propagates |
| The process was already started | LogicException |
The budget is not turned into the process's own timeout: symfony/process kills on timeout with no grace period, while the context's deadline goes through the same SIGTERM → grace → SIGKILL path as a cancellation.
| Class | Description |
|---|---|
ContextProcess |
run(Process $process, Context $context, ?callable $callback = null, float $gracePeriod = 2.0, float $pollInterval = 0.05): int |
Arguments are passed as an argv array to symfony/process, never through a shell. The package adds no input of its own to the command line.
See examples/ for runnable scripts.
| Script | Shows | Needs server? |
|---|---|---|
budget.php |
A 10 s command stopped by a 0.5 s budget; a command within the budget returning its exit code and output | no |
No PHP/Composer on the host — run in Docker via the composer:2 image:
make install
make build
make cs-fix
make test
make test-coverage
make mutation
make release-check