Skip to content

Commit 02c560e

Browse files
authored
Merge pull request #64 from clue-labs/pipes
Expose all standard I/O pipes in an array
2 parents 3f695fa + 78a3ca7 commit 02c560e

File tree

4 files changed

+93
-26
lines changed

4 files changed

+93
-26
lines changed

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,21 @@ See also the [examples](examples).
5252

5353
Once a process is started, its I/O streams will be constructed as instances of
5454
`React\Stream\ReadableStreamInterface` and `React\Stream\WritableStreamInterface`.
55-
Before `start()` is called, these properties are `null`.Once a process terminates,
55+
Before `start()` is called, these properties are not set. Once a process terminates,
5656
the streams will become closed but not unset.
5757

58-
* `$stdin`
59-
* `$stdout`
60-
* `$stderr`
58+
* `$stdin` or `$pipes[0]` is a `WritableStreamInterface`
59+
* `$stdout` or `$pipes[1]` is a `ReadableStreamInterface`
60+
* `$stderr` or `$pipes[2]` is a `ReadableStreamInterface`
6161

62-
Each of these implement the underlying
62+
Following common Unix conventions, this library will always start each child
63+
process with the three pipes matching the standard I/O streams as given above.
64+
You can use the named references for common use cases or access these as an
65+
array with all three pipes.
66+
67+
Because each of these implement the underlying
6368
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) or
64-
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface) and
69+
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface),
6570
you can use any of their events and methods as usual:
6671

6772
```php
@@ -255,10 +260,10 @@ $process = new Process('sleep 10');
255260
$process->start($loop);
256261

257262
$loop->addTimer(2.0, function () use ($process) {
258-
$process->stdin->close();
259-
$process->stdout->close();
260-
$process->stderr->close();
261-
$process->terminate(SIGKILL);
263+
foreach ($process->pipes as $pipe) {
264+
$pipe->close();
265+
}
266+
$process->terminate();
262267
});
263268
```
264269

examples/04-terminate.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
// forcefully terminate process after 2s
2020
$loop->addTimer(2.0, function () use ($process) {
21-
$process->stdin->close();
22-
$process->stdout->close();
23-
$process->stderr->close();
21+
foreach ($process->pipes as $pipe) {
22+
$pipe->close();
23+
}
2424
$process->terminate();
2525
});
2626

src/Process.php

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use Evenement\EventEmitter;
66
use React\EventLoop\LoopInterface;
77
use React\Stream\ReadableResourceStream;
8+
use React\Stream\ReadableStreamInterface;
89
use React\Stream\WritableResourceStream;
10+
use React\Stream\WritableStreamInterface;
911

1012
/**
1113
* Process component.
@@ -17,15 +19,36 @@
1719
*/
1820
class Process extends EventEmitter
1921
{
22+
/**
23+
* @var ?WritableStreamInterface
24+
*/
2025
public $stdin;
26+
27+
/**
28+
* @var ?ReadableStreamInterface
29+
*/
2130
public $stdout;
31+
32+
/**
33+
* @var ?ReadableStreamInterface
34+
*/
2235
public $stderr;
2336

37+
/**
38+
* Array with all process pipes (once started)
39+
* - 0: STDIN (`WritableStreamInterface`)
40+
* - 1: STDOUT (`ReadableStreamInterface`)
41+
* - 2: STDERR (`ReadableStreamInterface`)
42+
*
43+
* @var ReadableStreamInterface|WritableStreamInterface
44+
*/
45+
public $pipes = array();
46+
2447
private $cmd;
2548
private $cwd;
2649
private $env;
2750
private $enhanceSigchildCompatibility;
28-
private $pipes;
51+
private $sigchildPipe;
2952

3053
private $process;
3154
private $status;
@@ -90,13 +113,15 @@ public function start(LoopInterface $loop, $interval = 0.1)
90113
array('pipe', 'w'), // stderr
91114
);
92115

116+
$sigchild = null;
93117
// Read exit code through fourth pipe to work around --enable-sigchild
94118
if ($this->enhanceSigchildCompatibility) {
95119
$fdSpec[] = array('pipe', 'w');
96-
$cmd = sprintf('(%s) 3>/dev/null; code=$?; echo $code >&3; exit $code', $cmd);
120+
$sigchild = 3;
121+
$cmd = sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
97122
}
98123

99-
$this->process = proc_open($cmd, $fdSpec, $this->pipes, $this->cwd, $this->env);
124+
$this->process = proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env);
100125

101126
if (!is_resource($this->process)) {
102127
throw new \RuntimeException('Unable to launch a new process.');
@@ -129,11 +154,24 @@ public function start(LoopInterface $loop, $interval = 0.1)
129154
});
130155
};
131156

132-
$this->stdin = new WritableResourceStream($this->pipes[0], $loop);
133-
$this->stdout = new ReadableResourceStream($this->pipes[1], $loop);
134-
$this->stdout->on('close', $streamCloseHandler);
135-
$this->stderr = new ReadableResourceStream($this->pipes[2], $loop);
136-
$this->stderr->on('close', $streamCloseHandler);
157+
if ($sigchild !== null) {
158+
$this->sigchildPipe = $pipes[$sigchild];
159+
unset($pipes[$sigchild]);
160+
}
161+
162+
foreach ($pipes as $n => $fd) {
163+
if ($n === 0) {
164+
$stream = new WritableResourceStream($fd, $loop);
165+
} else {
166+
$stream = new ReadableResourceStream($fd, $loop);
167+
$stream->on('close', $streamCloseHandler);
168+
}
169+
$this->pipes[$n] = $stream;
170+
}
171+
172+
$this->stdin = $this->pipes[0];
173+
$this->stdout = $this->pipes[1];
174+
$this->stderr = $this->pipes[2];
137175
}
138176

139177
/**
@@ -337,11 +375,11 @@ public final static function setSigchildEnabled($sigchild)
337375
*/
338376
private function pollExitCodePipe()
339377
{
340-
if ( ! isset($this->pipes[3])) {
378+
if ($this->sigchildPipe === null) {
341379
return;
342380
}
343381

344-
$r = array($this->pipes[3]);
382+
$r = array($this->sigchildPipe);
345383
$w = $e = null;
346384

347385
$n = @stream_select($r, $w, $e, 0);
@@ -364,12 +402,12 @@ private function pollExitCodePipe()
364402
*/
365403
private function closeExitCodePipe()
366404
{
367-
if ( ! isset($this->pipes[3])) {
405+
if ($this->sigchildPipe === null) {
368406
return;
369407
}
370408

371-
fclose($this->pipes[3]);
372-
unset($this->pipes[3]);
409+
fclose($this->sigchildPipe);
410+
$this->sigchildPipe = null;
373411
}
374412

375413
/**

tests/AbstractProcessTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ public function testGetCommand()
1818
$this->assertSame('echo foo', $process->getCommand());
1919
}
2020

21+
public function testPipesWillBeUnsetBeforeStarting()
22+
{
23+
$process = new Process('echo foo');
24+
25+
$this->assertNull($process->stdin);
26+
$this->assertNull($process->stdout);
27+
$this->assertNull($process->stderr);
28+
$this->assertEquals(array(), $process->pipes);
29+
}
30+
31+
public function testStartWillAssignPipes()
32+
{
33+
$process = new Process('echo foo');
34+
$process->start($this->createLoop());
35+
36+
$this->assertInstanceOf('React\Stream\WritableStreamInterface', $process->stdin);
37+
$this->assertInstanceOf('React\Stream\ReadableStreamInterface', $process->stdout);
38+
$this->assertInstanceOf('React\Stream\ReadableStreamInterface', $process->stderr);
39+
$this->assertCount(3, $process->pipes);
40+
$this->assertSame($process->stdin, $process->pipes[0]);
41+
$this->assertSame($process->stdout, $process->pipes[1]);
42+
$this->assertSame($process->stderr, $process->pipes[2]);
43+
}
44+
2145
public function testIsRunning()
2246
{
2347
$process = new Process('sleep 1');

0 commit comments

Comments
 (0)