Skip to content

Commit 74da6d5

Browse files
authored
Merge pull request #90 from clue-labs/close-socket
2 parents 45e6e3a + 8c9117f commit 74da6d5

File tree

2 files changed

+154
-7
lines changed

2 files changed

+154
-7
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: 152 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,28 @@ public function testStartWithExcessiveNumberOfFileDescriptorsWillThrow()
156156
$this->markTestSkipped('Unable to determine limit of open files (ulimit not available?)');
157157
}
158158

159+
// create 70% (usually ~700) dummy file handles in this parent
160+
$limit = (int) ($ulimit * 0.7);
161+
162+
$memory = ini_get('memory_limit');
163+
if ($memory === '-1') {
164+
$memory = PHP_INT_MAX;
165+
} elseif (preg_match('/^\d+G$/i', $memory)) {
166+
$memory = ((int) $memory) * 1024 * 1024 * 1024;
167+
} elseif (preg_match('/^\d+M$/i', $memory)) {
168+
$memory = ((int) $memory) * 1024 * 1024;
169+
} elseif (preg_match('/^\d+K$/i', $memory)) {
170+
$memory = ((int) $memory) * 1024;
171+
}
172+
173+
// each file descriptor takes ~600 bytes of memory, so skip test if this would exceed memory_limit
174+
if ($limit * 600 > $memory) {
175+
$this->markTestSkipped('Test requires ~' . round($limit * 600 / 1024 / 1024) . '/' . round($memory / 1024 / 1024) . ' MiB memory with ' . $ulimit . ' file descriptors');
176+
}
177+
159178
$loop = $this->createLoop();
160179

161-
// create 70% (usually ~700) dummy file handles in this parent dummy
180+
// create ~700 dummy file handles in this parent
162181
$limit = (int) ($ulimit * 0.7);
163182
$fds = array();
164183
for ($i = 0; $i < $limit; ++$i) {
@@ -579,6 +598,31 @@ public function testProcessWillExitFasterThanExitInterval()
579598
$this->assertLessThan(0.1, $time);
580599
}
581600

601+
/**
602+
* @requires PHP 8
603+
*/
604+
public function testProcessWithSocketIoWillExitFasterThanExitInterval()
605+
{
606+
$loop = $this->createLoop();
607+
$process = new Process(
608+
DIRECTORY_SEPARATOR === '\\' ? 'cmd /c echo hi' : 'echo hi',
609+
null,
610+
null,
611+
array(
612+
array('socket'),
613+
array('socket'),
614+
array('socket')
615+
)
616+
);
617+
$process->start($loop, 2);
618+
619+
$time = microtime(true);
620+
$loop->run();
621+
$time = microtime(true) - $time;
622+
623+
$this->assertLessThan(0.1, $time);
624+
}
625+
582626
public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
583627
{
584628
if (DIRECTORY_SEPARATOR === '\\') {
@@ -606,6 +650,39 @@ public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
606650
$this->assertTrue($closed);
607651
}
608652

653+
/**
654+
* @requires PHP 8
655+
*/
656+
public function testDetectsClosingStdoutSocketWithoutHavingToWaitForExit()
657+
{
658+
$loop = $this->createLoop();
659+
$process = new Process(
660+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDOUT); sleep(1);'),
661+
null,
662+
null,
663+
array(
664+
array('socket'),
665+
array('socket'),
666+
array('socket')
667+
)
668+
);
669+
$process->start($loop);
670+
671+
$closed = false;
672+
$process->stdout->on('close', function () use (&$closed, $loop) {
673+
$closed = true;
674+
$loop->stop();
675+
});
676+
677+
// run loop for maximum of 0.5s only
678+
$loop->addTimer(0.5, function () use ($loop) {
679+
$loop->stop();
680+
});
681+
$loop->run();
682+
683+
$this->assertTrue($closed);
684+
}
685+
609686
public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
610687
{
611688
if (DIRECTORY_SEPARATOR === '\\') {
@@ -642,6 +719,48 @@ public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
642719
$this->assertTrue($process->isRunning());
643720
}
644721

722+
/**
723+
* @requires PHP 8
724+
*/
725+
public function testKeepsRunningEvenWhenAllStdioSocketsHaveBeenClosed()
726+
{
727+
$loop = $this->createLoop();
728+
$process = new Process(
729+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);sleep(1);'),
730+
null,
731+
null,
732+
array(
733+
array('socket'),
734+
array('socket'),
735+
array('socket')
736+
)
737+
);
738+
$process->start($loop);
739+
740+
$closed = 0;
741+
$process->stdout->on('close', function () use (&$closed, $loop) {
742+
++$closed;
743+
if ($closed === 2) {
744+
$loop->stop();
745+
}
746+
});
747+
$process->stderr->on('close', function () use (&$closed, $loop) {
748+
++$closed;
749+
if ($closed === 2) {
750+
$loop->stop();
751+
}
752+
});
753+
754+
// run loop for maximum 0.5s only
755+
$loop->addTimer(0.5, function () use ($loop) {
756+
$loop->stop();
757+
});
758+
$loop->run();
759+
760+
$this->assertEquals(2, $closed);
761+
$this->assertTrue($process->isRunning());
762+
}
763+
645764
public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
646765
{
647766
if (DIRECTORY_SEPARATOR === '\\') {
@@ -662,16 +781,42 @@ public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
662781
$this->assertSame(0, $process->getExitCode());
663782
}
664783

665-
public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
784+
/**
785+
* @requires PHP 8
786+
*/
787+
public function testDetectsClosingProcessEvenWhenAllStdioSocketsHaveBeenClosed()
666788
{
667789
$loop = $this->createLoop();
790+
$process = new Process(
791+
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);usleep(10000);'),
792+
null,
793+
null,
794+
array(
795+
array('socket'),
796+
array('socket'),
797+
array('socket')
798+
)
799+
);
800+
$process->start($loop, 0.001);
668801

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-
}
802+
$time = microtime(true);
803+
$loop->run();
804+
$time = microtime(true) - $time;
805+
806+
$this->assertLessThan(0.5, $time);
807+
$this->assertSame(0, $process->getExitCode());
808+
}
674809

810+
public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
811+
{
812+
$loop = $this->createLoop();
813+
814+
$process = new Process(
815+
(DIRECTORY_SEPARATOR === '\\' ? 'cmd /c ' : '') . 'exit 0',
816+
null,
817+
null,
818+
array()
819+
);
675820
$process->start($loop, 0.001);
676821

677822
$time = microtime(true);

0 commit comments

Comments
 (0)