PHP Workers
To execute your PHP application, you need to create a worker endpoint and configure RoadRunner to utilize it. First, install the required packages using Composer:
composer require spiral/roadrunner nyholm/psr7
The simplest entry point using the PSR-7 server API looks like this:
<?php
use Spiral\RoadRunner;
use Nyholm\Psr7;
include "vendor/autoload.php";
$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();
$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
while ($req = $worker->waitRequest()) {
try {
$response = new Psr7\Response();
$response->getBody()->write('Hello world!');
$worker->respond($response);
} catch (\Throwable $e) {
$worker->getWorker()->error((string)$e);
}
}
The worker expects to communicate with the parent RoadRunner server through standard pipes. Create a .rr.yaml configuration file to enable it:
server:
command: "php psr-worker.php"
http:
address: 0.0.0.0:8080
pool:
num_workers: 4
If you prefer JSON over YAML, use .rr.json instead:
{
"server": {
"command": "path-to-php/php psr-worker.php"
},
"http": {
"address": "0.0.0.0:8080",
"pool": {
"num_workers": 4
}
}
}
Start the application by downloading the RR binary and running rr serve.
Alternative Communication Methods
PHP Workers use standard pipes (STDOUT and STDERR) to exchange data frames with the RR server. In some cases, you may want alternative communication methods such as TCP sockets:
server:
command: "php psr-worker.php"
relay: "tcp://localhost:7000"
http:
address: 0.0.0.0:8080
pool:
num_workers: 4
Or Unix sockets:
server:
command: "php psr-worker.php"
relay: "unix://rr.sock"
http:
address: 0.0.0.0:8080
pool:
num_workers: 4
Troubleshooting
In certain situations, RR may fail to handle errors produced by PHP workers (PHP missing, script deadlocks, etc.).
$ rr serve
DEBU[0003] [rpc]: started
DEBU[0003] [http]: started
ERRO[0003] [http]: unable to connect to worker: unexpected response, header is missing: exit status 1
DEBU[0003] [rpc]: stopped
You can resolve this by manually invoking the command set in the .rr file:
$ php psr-worker.php
Before providing any input, the worker should not cause any errors and must fail with an invalid input signature after the first input character.
Other Worker Types
Different RoadRunner implementations may define their own worker APIs, such as gRPC and Workflow/Activity Workers.
Environment Configuration
All RoadRunner workers inherit system configuration available to the parent server process. Additionally, you can customize ENV variables passed to workers using the env section in your .rr configuration file:
server:
command: "php worker.php"
env:
key: value
All keys will be automatically uppercase!
Default ENV Values
RoadRunner provides a set of ENV values to help PHP processes identify how to communicate correctly with the server:
| Key | Description |
|---|---|
| RR_MODE | Determines the mode the worker should use ("http", "temporal") |
| RR_RPC | Contains RPC connection address when enabled |
| RR_RELAY | "pipe" or "tcp://..." depending on server relay configuration |
Developer Mode
RoadRunner runs PHP scripts in daemon mode, meaning the server must be reloaded every time you change the codebase.
If you use any modern IDE, you can implement this by adding a file watcher that automatically calls rr reset for the plugin specified in the reload configuration.
Alternatively, use the auto-resetter.
Runing in Docker
You can reset the RR process by connecting to docker using a local RR client:
rpc:
listen: tcp://:6001
Ensure you forward/expose port 6001.
Then run rr reset local when file changes occur.
Debug Mode
Run workers in debug mode (similar to PHP-FPM):
http:
pool.debug: true
Error Handling
There are several methods to handle errors produced by PHP workers.
The simplest and most common approach is to respond to the parent service using getWorker()->error():
try {
$resp = new \Zend\Diactoros\Response();
$resp->getBody()->write("hello world");
$psr7->respond($resp);
} catch (\Throwable $e) {
$psr7->getWorker()->error((string)$e);
}
You can also flush warnings and errors to STDERR to output them directly to the console (similar to docker-compose):
file_put_contents('php://stderr', 'my message');
Since RoadRunner 2.0, all warnings sent to STDOUT are also forwarded to STDERR.
Restarting Workers
RoadRunner provides multiple methods to safely restart workers on demand. Both methods can be used on live servers without causing downtime.
Stop Command
You can send a stop command from the worker to the parent server to force process destruction. In this case, jobs/requests are automatically forwarded to the next worker.
We can demonstrate this by implementing control via max_jobs on the PHP side:
<?php
use Spiral\RoadRunner;
use Nyholm\Psr7;
include "vendor/autoload.php";
$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();
$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
$count = 0;
while ($req = $worker->waitRequest()) {
try {
$response = new Psr7\Response();
$response->getBody()->write('Hello world!');
$count++;
if ($count > 10) {
$worker->getWorker()->stop();
return;
}
$worker->respond($response);
} catch (\Throwable $e) {
$worker->getWorker()->error((string)$e);
}
}
This method can be used to control memory usage within PHP scripts.
Full Reset
You can also trigger reconstruction of all RoadRunner workers using the embedded RPC bus:
$rpc = \Spiral\Goridge\RPC\RPC::create('tcp://127.0.0.1:6001');
$rpc->call('resetter.Reset', 'http');
Embedded Monitoring
RoadRunner can monitor your application and run soft resets when necessary (between requests). Previously named limit, now called supervisor.
Configuration
Edit your .rr file to specify application limits:
http:
address: "0.0.0.0:8080"
pool:
num_workers: 6
supervisor:
# watch_tick defines how often to check the state of the workers (seconds)
watch_tick: 1s
# ttl defines maximum time worker is allowed to live (seconds)
ttl: 0
# idle_ttl defines maximum duration worker can spend in idle mode after first use. Disabled when 0 (seconds)
idle_ttl: 10s
# exec_ttl defines maximum lifetime per job (seconds)
exec_ttl: 10s
# max_worker_memory limits memory usage per worker (MB)
max_worker_memory: 100
RPC to Application Server
You can connect to the application server via SocketRelay:
$rpc = \Spiral\Goridge\RPC\RPC::create('tcp://127.0.0.1:6001');
You can immediately use this RPC to call embedded RPC services, for example HTTP:
var_dump($rpc->call('informer.Workers', 'http'));
Note: When running workers in debug mode (
http: { debug: true }in.rr.yaml), the number of http workers will be zero (returns an empty array). This behavior may change in the future and you should not rely on this result to check if RoadRunner started in development mode.
You can read how to create your own services and RPC methods in this section.
Important Notes
File Uploads
Since file uploads are handled on the RR side, the PHP process only receives the temporary file name. This resource is not registered in the uploaded files hash, therefore the is_uploaded_file function will always return false.
Exit and Die Functions
Note that you should not use any of the following methods: die, exit. If your library needs to write content to standard output, use buffered output.
Caching
Using RoadRunner on Windows with the WinCache extension may cause worker bytecode to get stuck in memory.
Debugging
You can use RoadRunner scripts with the xDebug extension. To enable it, configure your IDE to accept remote connections.
Note: If you run multiple PHP processes, you must expand the maximum number of connections to match the number of active workers, otherwise some calls will not be caught at your breakpoints.
To activate xDebug, ensure xdebug.mode=debug is set in your php.ini.
To enable xDebug in your application, ensure you set the XDEBUG_SESSION ENV variable:
rpc:
listen: tcp://127.0.0.1:6001
server:
command: "php worker.php"
env:
XDEBUG_SESSION: 1
http:
address: "0.0.0.0:8080"
pool:
num_workers: 1
debug: true
You should now be able to use breakpoints and inspect state.