Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
/vendor/
/composer.lock
/phpunit.xml
/.phpunit.cache/
/tests/fixtures/*.log
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
cocur/background-process
========================

> Start processes in the background that continue running when the PHP process exists.
> Start processes in the background that continue running when the PHP process exits.

[![Latest Stable Version](http://img.shields.io/packagist/v/cocur/background-process.svg)](https://packagist.org/packages/cocur/background-process)
[![Build Status](http://img.shields.io/travis/cocur/background-process.svg)](https://travis-ci.org/cocur/background-process)
[![Windows Build status](https://ci.appveyor.com/api/projects/status/odmyynd522vuef1y?svg=true)](https://ci.appveyor.com/project/florianeckerstorfer/background-process)
[![Code Coverage](https://scrutinizer-ci.com/g/cocur/background-process/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/cocur/background-process/?branch=master)

**FrankenPHP worker mode: Supported** — see [docs/FRANKENPHP.md](docs/FRANKENPHP.md).


Installation
------------
Expand All @@ -18,6 +20,8 @@ You can install Cocur\BackgroundProcess using [Composer](http://getcomposer.org)
$ composer require cocur/background-process
```

Requirements: **PHP >= 8.1** and `symfony/service-contracts` (for `ResetInterface` / worker reset).


Usage
-----
Expand Down Expand Up @@ -48,6 +52,9 @@ while ($process->isRunning()) {
echo "\nDone.\n";
```

> **FrankenPHP worker mode:** do **not** use the `while (isRunning()) { sleep(1); }` pattern inside an HTTP
> request — it blocks the worker thread. Fire the process, persist the PID, and return. See [docs/FRANKENPHP.md](docs/FRANKENPHP.md).

If the process runs you can stop it:

```php
Expand All @@ -60,6 +67,18 @@ if ($process->isRunning()) {
*Please note: If the parent process continues to run while the child process(es) run(s) in the background you should
use a more robust solution, for example, the [Symfony Process](https://github.com/symfony/Process) component.*

### Factory (recommended for DI / FrankenPHP)

`Factory` is stateless and safe as a shared service. Always create a new `BackgroundProcess` per operation:

```php
use Cocur\BackgroundProcess\Factory;

$factory = new Factory();
$process = $factory->newProcess('bin/console app:job');
$process->run();
```

### Windows Support

Since Version 0.5 Cocur\BackgroundProcess has basic support for Windows included. However, support is very limited at
Expand All @@ -84,9 +103,26 @@ $process->isRunning(); // -> true
$process->stop(); // -> true
```

### FrankenPHP worker mode

Compatible with FrankenPHP classic and worker mode. `BackgroundProcess` implements `ResetInterface` so Symfony can
clear `$command` / `$pid` between requests when the instance is a shared service.

Full guidance: [docs/FRANKENPHP.md](docs/FRANKENPHP.md).


Change Log
----------

### Version 0.8 (18 July 2026)

- FrankenPHP **worker mode** compatibility: implement `ResetInterface`, add `reset()`, document safe usage
- Require PHP >= 8.1 and `symfony/service-contracts`
- Prefer `Factory` as shared DI service; create a fresh process per operation
- Guard when `shell_exec` is disabled; treat missing PID as not running
- Detect running processes via `/proc` on Linux (no `ps` binary required)
- Escape output file path on *nix; modernize tests for PHPUnit 10/11

### Version 0.7 (11 February 2017)

- [#19](https://github.com/cocur/background-process/pull/19) Create `BackgroundProcess` object from PID (by [socieboy](https://github.com/socieboy) and [florianeckerstorfer](https://github.com/florianeckerstorfer))
Expand Down
20 changes: 16 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
{
"name": "cocur/background-process",
"description": "Start processes in the background that continue running when the PHP process exists.",
"description": "Start processes in the background that continue running when the PHP process exits. Compatible with FrankenPHP worker mode.",
"type": "library",
"keywords": ["process", "background", "unix"],
"keywords": ["process", "background", "unix", "frankenphp", "worker"],
"license": "MIT",
"require": {
"php": ">=5.5"
"php": ">=8.1",
"symfony/service-contracts": "^2.5|^3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8|~5.1"
"phpunit/phpunit": "^10.5|^11.0"
},
"autoload": {
"psr-4": {
"Cocur\\BackgroundProcess\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Cocur\\BackgroundProcess\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit"
},
"config": {
"sort-packages": true
}
}
73 changes: 73 additions & 0 deletions docs/FRANKENPHP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# FrankenPHP (runtime and worker mode)

This library is **compatible with FrankenPHP** in classic HTTP mode and in **worker mode**.

## Compatibility summary

| Concern | Status |
|---------|--------|
| Mutable class static state | None (only `createFromPID()` as a pure factory) |
| Globals / `$_ENV` writes | None |
| `exit()` / `die()` / `pcntl_fork` | None |
| Request-scoped instance state | `$command`, `$pid` — cleared by `reset()` |
| Shared DI service | Use `Factory` (stateless) or tag `BackgroundProcess` with `kernel.reset` |
| `isRunning()` on Linux | Uses `/proc/<pid>` (works when `ps` is not installed in the image) |

**FrankenPHP worker mode: Supported.**

## Recommended usage under worker mode

### 1. Prefer a fresh instance per operation

```php
use Cocur\BackgroundProcess\Factory;

// Register Factory as a shared service — safe across requests
$factory = $container->get(Factory::class);
$process = $factory->newProcess('bin/console app:heavy-job');
$process->run();

// Persist PID if you need to manage the OS process later
$pid = $process->getPid();
```

### 2. If you inject `BackgroundProcess` as a shared service

Implementations of `Symfony\Contracts\Service\ResetInterface` are reset by Symfony between requests when the kernel runs in a long-lived worker (FrankenPHP, `runtime/frankenphp-symfony`, etc.).

```yaml
services:
Cocur\BackgroundProcess\BackgroundProcess:
shared: true
tags:
- { name: kernel.reset, method: reset }
```

`reset()` clears `$command` and `$pid` on the PHP object. It does **not** kill the OS child. Store the PID (DB, Redis, file) before the request ends if you still need `isRunning()` / `stop()` later via `BackgroundProcess::createFromPID($pid)`.

### 3. Do not block the worker thread

Avoid polling loops inside an HTTP request handled by a FrankenPHP worker:

```php
// BAD in worker mode — blocks the worker until the child exits
while ($process->isRunning()) {
sleep(1);
}
```

Fire the process, persist the PID, and return. Check status from another request, a Messenger handler, or a cron/CLI command.

### 4. `shell_exec` must be available

FrankenPHP images sometimes disable `shell_exec` via `disable_functions`. This library throws a clear `RuntimeException` when it is unavailable. Alternatives: Symfony Messenger, a dedicated CLI worker, or `symfony/process` when the parent stays alive.

## Runtime (non-worker) mode

Behaviour is identical to PHP-FPM / classic SAPIs: each request gets a new PHP process, so instance state never leaks. Worker-specific rules above still apply as good practice.

## Recommendations

- Limit requests per worker (`frankenphp_loop_max` / `MAX_REQUESTS`) so workers recycle and memory is released.
- Do not register a single `BackgroundProcess` instance with a live PID as an application-wide singleton without `reset()`.
- Prefer queues for work that must outlive the HTTP request in production Symfony apps.
15 changes: 9 additions & 6 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php" colors="true">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="cocur/background-process Test Suite">
<directory>tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<source>
<include>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</include>
</source>
</phpunit>
Loading