Skip to content

Commit 3f695fa

Browse files
authored
Merge pull request #63 from clue-labs/sigchild
Improve sigchild compatibility and support explicit configuration
2 parents ac6c7f9 + 2c9bfc5 commit 3f695fa

File tree

3 files changed

+63
-66
lines changed

3 files changed

+63
-66
lines changed

README.md

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -295,17 +295,39 @@ timeout.
295295

296296
### Sigchild Compatibility
297297

298-
When PHP has been compiled with the `--enabled-sigchild` option, a child
299-
process' exit code cannot be reliably determined via `proc_close()` or
300-
`proc_get_status()`. Instead, we execute the child process with a fourth pipe
301-
and use that to retrieve its exit code.
298+
Internally, this project uses a work-around to improve compatibility when PHP
299+
has been compiled with the `--enable-sigchild` option. This should not affect most
300+
installations as this configure option is not used by default and many
301+
distributions (such as Debian and Ubuntu) are known to not use this by default.
302+
Some installations that use [Oracle OCI8](http://php.net/manual/en/book.oci8.php)
303+
may use this configure option to circumvent `defunct` processes.
304+
305+
When PHP has been compiled with the `--enable-sigchild` option, a child process'
306+
exit code cannot be reliably determined via `proc_close()` or `proc_get_status()`.
307+
To work around this, we execute the child process with an additional pipe and
308+
use that to retrieve its exit code.
309+
310+
This work-around incurs some overhead, so we only trigger this when necessary
311+
and when we detect that PHP has been compiled with the `--enable-sigchild` option.
312+
Because PHP does not provide a way to reliably detect this option, we try to
313+
inspect output of PHP's configure options from the `phpinfo()` function.
314+
315+
The static `setSigchildEnabled(bool $sigchild): void` method can be used to
316+
explicitly enable or disable this behavior like this:
302317

303-
This behavior is used by default and only when necessary. It may be manually
304-
disabled by calling `setEnhanceSigchildCompatibility(false)` on the Process
305-
before it is started, in which case the `exit` event may receive `null` instead
306-
of the actual exit code.
318+
```php
319+
// advanced: not recommended by default
320+
Process::setSigchildEnabled(true);
321+
```
322+
323+
Note that all processes instantiated after this method call will be affected.
324+
If this work-around is disabled on an affected PHP installation, the `exit`
325+
event may receive `null` instead of the actual exit code as described above.
326+
Similarly, some distributions are known to omit the configure options from
327+
`phpinfo()`, so automatic detection may fail to enable this work-around in some
328+
cases. You may then enable this explicitly as given above.
307329

308-
**Note:** This functionality was taken from Symfony's
330+
**Note:** The original functionality was taken from Symfony's
309331
[Process](https://github.com/symfony/process) compoment.
310332

311333
### Windows Compatibility

src/Process.php

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function __construct($cmd, $cwd = null, array $env = null)
6464
}
6565
}
6666

67-
$this->enhanceSigchildCompatibility = $this->isSigchildEnabled();
67+
$this->enhanceSigchildCompatibility = self::isSigchildEnabled();
6868
}
6969

7070
/**
@@ -91,7 +91,7 @@ public function start(LoopInterface $loop, $interval = 0.1)
9191
);
9292

9393
// Read exit code through fourth pipe to work around --enable-sigchild
94-
if ($this->isSigchildEnabled() && $this->enhanceSigchildCompatibility) {
94+
if ($this->enhanceSigchildCompatibility) {
9595
$fdSpec[] = array('pipe', 'w');
9696
$cmd = sprintf('(%s) 3>/dev/null; code=$?; echo $code >&3; exit $code', $cmd);
9797
}
@@ -152,7 +152,7 @@ public function close()
152152
$this->stdout->close();
153153
$this->stderr->close();
154154

155-
if ($this->isSigchildEnabled() && $this->enhanceSigchildCompatibility) {
155+
if ($this->enhanceSigchildCompatibility) {
156156
$this->pollExitCodePipe();
157157
$this->closeExitCodePipe();
158158
}
@@ -203,38 +203,6 @@ public function getCommand()
203203
return $this->cmd;
204204
}
205205

206-
/**
207-
* Return whether sigchild compatibility is enabled.
208-
*
209-
* @return boolean
210-
*/
211-
public final function getEnhanceSigchildCompatibility()
212-
{
213-
return $this->enhanceSigchildCompatibility;
214-
}
215-
216-
/**
217-
* Enable or disable sigchild compatibility mode.
218-
*
219-
* Sigchild compatibility mode is required to get the exit code and
220-
* determine the success of a process when PHP has been compiled with
221-
* the --enable-sigchild option.
222-
*
223-
* @param boolean $enhance
224-
* @return self
225-
* @throws RuntimeException If the process is already running
226-
*/
227-
public final function setEnhanceSigchildCompatibility($enhance)
228-
{
229-
if ($this->isRunning()) {
230-
throw new \RuntimeException('Process is already running');
231-
}
232-
233-
$this->enhanceSigchildCompatibility = (bool) $enhance;
234-
235-
return $this;
236-
}
237-
238206
/**
239207
* Get the exit code returned by the process.
240208
*
@@ -347,6 +315,21 @@ public final static function isSigchildEnabled()
347315
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
348316
}
349317

318+
/**
319+
* Enable or disable sigchild compatibility mode.
320+
*
321+
* Sigchild compatibility mode is required to get the exit code and
322+
* determine the success of a process when PHP has been compiled with
323+
* the --enable-sigchild option.
324+
*
325+
* @param boolean $sigchild
326+
* @return void
327+
*/
328+
public final static function setSigchildEnabled($sigchild)
329+
{
330+
self::$sigchild = (bool) $sigchild;
331+
}
332+
350333
/**
351334
* Check the fourth pipe for an exit code.
352335
*

tests/AbstractProcessTest.php

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,6 @@ abstract class AbstractProcessTest extends TestCase
1111
{
1212
abstract public function createLoop();
1313

14-
public function testGetEnhanceSigchildCompatibility()
15-
{
16-
$process = new Process('echo foo');
17-
18-
$this->assertSame($process, $process->setEnhanceSigchildCompatibility(true));
19-
$this->assertTrue($process->getEnhanceSigchildCompatibility());
20-
21-
$this->assertSame($process, $process->setEnhanceSigchildCompatibility(false));
22-
$this->assertFalse($process->getEnhanceSigchildCompatibility());
23-
}
24-
25-
/**
26-
* @expectedException RuntimeException
27-
*/
28-
public function testSetEnhanceSigchildCompatibilityCannotBeCalledIfProcessIsRunning()
29-
{
30-
$process = new Process('sleep 1');
31-
32-
$process->start($this->createLoop());
33-
$process->setEnhanceSigchildCompatibility(false);
34-
}
35-
3614
public function testGetCommand()
3715
{
3816
$process = new Process('echo foo');
@@ -67,6 +45,20 @@ public function testGetTermSignalWhenRunning($process)
6745
$this->assertNull($process->getTermSignal());
6846
}
6947

48+
public function testCommandWithEnhancedSigchildCompatibilityReceivesExitCode()
49+
{
50+
$loop = $this->createLoop();
51+
$old = Process::isSigchildEnabled();
52+
Process::setSigchildEnabled(true);
53+
$process = new Process('echo foo');
54+
$process->start($loop);
55+
Process::setSigchildEnabled($old);
56+
57+
$loop->run();
58+
59+
$this->assertEquals(0, $process->getExitCode());
60+
}
61+
7062
public function testReceivesProcessStdoutFromEcho()
7163
{
7264
$cmd = 'echo test';

0 commit comments

Comments
 (0)