Skip to content

Commit 385e1aa

Browse files
committed
Documentation and examples for process termination
1 parent 20c54bb commit 385e1aa

3 files changed

Lines changed: 147 additions & 6 deletions

File tree

README.md

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ as [Streams](https://github.com/reactphp/stream).
1515

1616
* [Quickstart example](#quickstart-example)
1717
* [Processes](#processes)
18-
* [EventEmitter Events](#eventemitter-events)
1918
* [Methods](#methods)
2019
* [Stream Properties](#stream-properties)
2120
* [Command](#command)
21+
* [Termination](#termination)
2222
* [Sigchild Compatibility](#sigchild-compatibility)
2323
* [Windows Compatibility](#windows-compatibility)
2424
* [Install](#install)
@@ -48,11 +48,6 @@ See also the [examples](examples).
4848

4949
## Processes
5050

51-
### EventEmitter Events
52-
53-
* `exit`: Emitted whenever the process is no longer running. Event listeners
54-
will receive the exit code and termination signal as two arguments.
55-
5651
### Methods
5752

5853
* `start()`: Launches the process and registers its IO streams with the event
@@ -205,6 +200,103 @@ the wrapping shell.
205200
If you want to pass an invidual command only, you MAY want to consider
206201
prepending the command string with `exec` to avoid the wrapping shell.
207202

203+
### Termination
204+
205+
The `exit` event will be emitted whenever the process is no longer running.
206+
Event listeners will receive the exit code and termination signal as two
207+
arguments:
208+
209+
```php
210+
$process = new Process('sleep 10');
211+
$process->start($loop);
212+
213+
$process->on('exit', function ($code, $term) {
214+
if ($term === null) {
215+
echo 'exit with code ' . $code . PHP_EOL;
216+
} else {
217+
echo 'terminated with signal ' . $term . PHP_EOL;
218+
}
219+
});
220+
```
221+
222+
Note that `$code` is `null` if the process has terminated, but the exit
223+
code could not be determined (for example
224+
[sigchild compatibility](#sigchild-compatibility) was disabled).
225+
Similarly, `$term` is `null` unless the process has terminated in response to
226+
an uncaught signal sent to it.
227+
This is not a limitation of this project, but actual how exit codes and signals
228+
are exposed on POSIX systems, for more details see also
229+
[here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
230+
231+
It's also worth noting that process termination depends on all file descriptors
232+
being closed beforehand.
233+
This means that all [process pipes](#stream-properties) will emit a `close`
234+
event before the `exit` event and that no more `data` events will arrive after
235+
the `exit` event.
236+
Accordingly, if either of these pipes is in a paused state (`pause()` method
237+
or internally due to a `pipe()` call), this detection may not trigger.
238+
239+
The `terminate(?int $signal = null): bool` method can be used to send the
240+
process a signal (SIGTERM by default).
241+
Depending on which signal you send to the process and whether it has a signal
242+
handler registered, this can be used to either merely signal a process or even
243+
forcefully terminate it.
244+
245+
```php
246+
$process->terminate(SIGUSR1);
247+
```
248+
249+
Keep the above section in mind if you want to forcefully terminate a process.
250+
If your process spawn sub-processes or implicitly uses the
251+
[wrapping shell mentioned above](#command), its file descriptors may be
252+
inherited to child processes and terminating the main process may not
253+
necessarily terminate the whole process tree.
254+
It is highly suggested that you explicitly `close()` all process pipes
255+
accordingly when terminating a process:
256+
257+
```php
258+
$process = new Process('sleep 10');
259+
$process->start($loop);
260+
261+
$loop->addTimer(2.0, function () use ($process) {
262+
$process->stdin->close();
263+
$process->stout->close();
264+
$process->stderr->close();
265+
$process->terminate(SIGKILL);
266+
});
267+
```
268+
269+
For many simple programs these seamingly complicated steps can also be avoided
270+
by prefixing the command line with `exec` to avoid the wrapping shell and its
271+
inherited process pipes as [mentioned above](#command).
272+
273+
```php
274+
$process = new Process('exec sleep 10');
275+
$process->start($loop);
276+
277+
$loop->addTimer(2.0, function () use ($process) {
278+
$process->terminate();
279+
});
280+
```
281+
282+
Many command line programs also wait for data on `STDIN` and terminate cleanly
283+
when this pipe is closed.
284+
For example, the following can be used to "soft-close" a `cat` process:
285+
286+
```php
287+
$process = new Process('cat');
288+
$process->start($loop);
289+
290+
$loop->addTimer(2.0, function () use ($process) {
291+
$process->stdin->end();
292+
});
293+
```
294+
295+
While process pipes and termination may seem confusing to newcomers, the above
296+
properties actually allow some fine grained control over process termination,
297+
such as first trying a soft-close and then applying a force-close after a
298+
timeout.
299+
208300
### Sigchild Compatibility
209301

210302
When PHP has been compiled with the `--enabled-sigchild` option, a child

examples/04-terminate.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use React\EventLoop\Factory;
4+
use React\ChildProcess\Process;
5+
6+
require __DIR__ . '/../vendor/autoload.php';
7+
8+
$loop = Factory::create();
9+
10+
// start a process that takes 10s to terminate
11+
$process = new Process('sleep 10');
12+
$process->start($loop);
13+
14+
// report when process exits
15+
$process->on('exit', function ($exit, $term) {
16+
var_dump($exit, $term);
17+
});
18+
19+
// forcefully terminate process after 2s
20+
$loop->addTimer(2.0, function () use ($process) {
21+
$process->stdin->close();
22+
$process->stdout->close();
23+
$process->stderr->close();
24+
$process->terminate();
25+
});
26+
27+
$loop->run();

tests/AbstractProcessTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,28 @@ public function testTerminateProcesWithoutStartingReturnsFalse()
291291
$this->assertFalse($process->terminate());
292292
}
293293

294+
public function testTerminateWillExit()
295+
{
296+
$loop = $this->createloop();
297+
298+
$process = new Process('sleep 10');
299+
$process->start($loop);
300+
301+
$called = false;
302+
$process->on('exit', function () use (&$called) {
303+
$called = true;
304+
});
305+
306+
$process->stdin->close();
307+
$process->stdout->close();
308+
$process->stderr->close();
309+
$process->terminate();
310+
311+
$loop->run();
312+
313+
$this->assertTrue($called);
314+
}
315+
294316
public function testTerminateWithDefaultTermSignalUsingEventLoop()
295317
{
296318
if (defined('PHP_WINDOWS_VERSION_BUILD')) {

0 commit comments

Comments
 (0)