Skip to content

Commit 58363a1

Browse files
committed
Fix detecting closed socket pipes on PHP 8
1 parent 45e6e3a commit 58363a1

File tree

2 files changed

+134
-6
lines changed

2 files changed

+134
-6
lines changed

src/Process.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ public function start(LoopInterface $loop = null, $interval = 0.1)
245245

246246
if ($mode === 'r+') {
247247
$stream = new DuplexResourceStream($fd, $loop);
248+
$stream->on('close', $streamCloseHandler);
249+
$closeCount++;
248250
} elseif ($mode === 'w') {
249251
$stream = new WritableResourceStream($fd, $loop);
250252
} else {

tests/AbstractProcessTest.php

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,31 @@ public function testProcessWillExitFasterThanExitInterval()
579579
$this->assertLessThan(0.1, $time);
580580
}
581581

582+
/**
583+
* @requires PHP 8
584+
*/
585+
public function testProcessWithSocketIoWillExitFasterThanExitInterval()
586+
{
587+
$loop = $this->createLoop();
588+
$process = new Process(
589+
DIRECTORY_SEPARATOR === '\\' ? 'cmd /c echo hi' : 'echo hi',
590+
null,
591+
null,
592+
array(
593+
array('socket'),
594+
array('socket'),
595+
array('socket')
596+
)
597+
);
598+
$process->start($loop, 2);
599+
600+
$time = microtime(true);
601+
$loop->run();
602+
$time = microtime(true) - $time;
603+
604+
$this->assertLessThan(0.1, $time);
605+
}
606+
582607
public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
583608
{
584609
if (DIRECTORY_SEPARATOR === '\\') {
@@ -606,6 +631,39 @@ public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
606631
$this->assertTrue($closed);
607632
}
608633

634+
/**
635+
* @requires PHP 8
636+
*/
637+
public function testDetectsClosingStdoutSocketWithoutHavingToWaitForExit()
638+
{
639+
$loop = $this->createLoop();
640+
$process = new Process(
641+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDOUT); sleep(1);'),
642+
null,
643+
null,
644+
array(
645+
array('socket'),
646+
array('socket'),
647+
array('socket')
648+
)
649+
);
650+
$process->start($loop);
651+
652+
$closed = false;
653+
$process->stdout->on('close', function () use (&$closed, $loop) {
654+
$closed = true;
655+
$loop->stop();
656+
});
657+
658+
// run loop for maximum of 0.5s only
659+
$loop->addTimer(0.5, function () use ($loop) {
660+
$loop->stop();
661+
});
662+
$loop->run();
663+
664+
$this->assertTrue($closed);
665+
}
666+
609667
public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
610668
{
611669
if (DIRECTORY_SEPARATOR === '\\') {
@@ -642,6 +700,48 @@ public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
642700
$this->assertTrue($process->isRunning());
643701
}
644702

703+
/**
704+
* @requires PHP 8
705+
*/
706+
public function testKeepsRunningEvenWhenAllStdioSocketsHaveBeenClosed()
707+
{
708+
$loop = $this->createLoop();
709+
$process = new Process(
710+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);sleep(1);'),
711+
null,
712+
null,
713+
array(
714+
array('socket'),
715+
array('socket'),
716+
array('socket')
717+
)
718+
);
719+
$process->start($loop);
720+
721+
$closed = 0;
722+
$process->stdout->on('close', function () use (&$closed, $loop) {
723+
++$closed;
724+
if ($closed === 2) {
725+
$loop->stop();
726+
}
727+
});
728+
$process->stderr->on('close', function () use (&$closed, $loop) {
729+
++$closed;
730+
if ($closed === 2) {
731+
$loop->stop();
732+
}
733+
});
734+
735+
// run loop for maximum 0.5s only
736+
$loop->addTimer(0.5, function () use ($loop) {
737+
$loop->stop();
738+
});
739+
$loop->run();
740+
741+
$this->assertEquals(2, $closed);
742+
$this->assertTrue($process->isRunning());
743+
}
744+
645745
public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
646746
{
647747
if (DIRECTORY_SEPARATOR === '\\') {
@@ -662,16 +762,42 @@ public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
662762
$this->assertSame(0, $process->getExitCode());
663763
}
664764

665-
public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
765+
/**
766+
* @requires PHP 8
767+
*/
768+
public function testDetectsClosingProcessEvenWhenAllStdioSocketsHaveBeenClosed()
666769
{
667770
$loop = $this->createLoop();
771+
$process = new Process(
772+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);usleep(10000);'),
773+
null,
774+
null,
775+
array(
776+
array('socket'),
777+
array('socket'),
778+
array('socket')
779+
)
780+
);
781+
$process->start($loop, 0.001);
668782

669-
if (DIRECTORY_SEPARATOR === '\\') {
670-
$process = new Process('cmd /c exit 0', null, null, array());
671-
} else {
672-
$process = new Process('exit 0', null, null, array());
673-
}
783+
$time = microtime(true);
784+
$loop->run();
785+
$time = microtime(true) - $time;
786+
787+
$this->assertLessThan(0.5, $time);
788+
$this->assertSame(0, $process->getExitCode());
789+
}
674790

791+
public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
792+
{
793+
$loop = $this->createLoop();
794+
795+
$process = new Process(
796+
(DIRECTORY_SEPARATOR === '\\' ? 'cmd /c ' : '') . 'exit 0',
797+
null,
798+
null,
799+
array()
800+
);
675801
$process->start($loop, 0.001);
676802

677803
$time = microtime(true);

0 commit comments

Comments
 (0)