Skip to content

Commit

Permalink
Merge pull request #90 from clue-labs/close-socket
Browse files Browse the repository at this point in the history
  • Loading branch information
WyriHaximus authored Oct 11, 2021
2 parents 45e6e3a + 8c9117f commit 74da6d5
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ public function start(LoopInterface $loop = null, $interval = 0.1)

if ($mode === 'r+') {
$stream = new DuplexResourceStream($fd, $loop);
$stream->on('close', $streamCloseHandler);
$closeCount++;
} elseif ($mode === 'w') {
$stream = new WritableResourceStream($fd, $loop);
} else {
Expand Down
159 changes: 152 additions & 7 deletions tests/AbstractProcessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,28 @@ public function testStartWithExcessiveNumberOfFileDescriptorsWillThrow()
$this->markTestSkipped('Unable to determine limit of open files (ulimit not available?)');
}

// create 70% (usually ~700) dummy file handles in this parent
$limit = (int) ($ulimit * 0.7);

$memory = ini_get('memory_limit');
if ($memory === '-1') {
$memory = PHP_INT_MAX;
} elseif (preg_match('/^\d+G$/i', $memory)) {
$memory = ((int) $memory) * 1024 * 1024 * 1024;
} elseif (preg_match('/^\d+M$/i', $memory)) {
$memory = ((int) $memory) * 1024 * 1024;
} elseif (preg_match('/^\d+K$/i', $memory)) {
$memory = ((int) $memory) * 1024;
}

// each file descriptor takes ~600 bytes of memory, so skip test if this would exceed memory_limit
if ($limit * 600 > $memory) {
$this->markTestSkipped('Test requires ~' . round($limit * 600 / 1024 / 1024) . '/' . round($memory / 1024 / 1024) . ' MiB memory with ' . $ulimit . ' file descriptors');
}

$loop = $this->createLoop();

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

/**
* @requires PHP 8
*/
public function testProcessWithSocketIoWillExitFasterThanExitInterval()
{
$loop = $this->createLoop();
$process = new Process(
DIRECTORY_SEPARATOR === '\\' ? 'cmd /c echo hi' : 'echo hi',
null,
null,
array(
array('socket'),
array('socket'),
array('socket')
)
);
$process->start($loop, 2);

$time = microtime(true);
$loop->run();
$time = microtime(true) - $time;

$this->assertLessThan(0.1, $time);
}

public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
{
if (DIRECTORY_SEPARATOR === '\\') {
Expand Down Expand Up @@ -606,6 +650,39 @@ public function testDetectsClosingStdoutWithoutHavingToWaitForExit()
$this->assertTrue($closed);
}

/**
* @requires PHP 8
*/
public function testDetectsClosingStdoutSocketWithoutHavingToWaitForExit()
{
$loop = $this->createLoop();
$process = new Process(
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDOUT); sleep(1);'),
null,
null,
array(
array('socket'),
array('socket'),
array('socket')
)
);
$process->start($loop);

$closed = false;
$process->stdout->on('close', function () use (&$closed, $loop) {
$closed = true;
$loop->stop();
});

// run loop for maximum of 0.5s only
$loop->addTimer(0.5, function () use ($loop) {
$loop->stop();
});
$loop->run();

$this->assertTrue($closed);
}

public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
{
if (DIRECTORY_SEPARATOR === '\\') {
Expand Down Expand Up @@ -642,6 +719,48 @@ public function testKeepsRunningEvenWhenAllStdioPipesHaveBeenClosed()
$this->assertTrue($process->isRunning());
}

/**
* @requires PHP 8
*/
public function testKeepsRunningEvenWhenAllStdioSocketsHaveBeenClosed()
{
$loop = $this->createLoop();
$process = new Process(
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);sleep(1);'),
null,
null,
array(
array('socket'),
array('socket'),
array('socket')
)
);
$process->start($loop);

$closed = 0;
$process->stdout->on('close', function () use (&$closed, $loop) {
++$closed;
if ($closed === 2) {
$loop->stop();
}
});
$process->stderr->on('close', function () use (&$closed, $loop) {
++$closed;
if ($closed === 2) {
$loop->stop();
}
});

// run loop for maximum 0.5s only
$loop->addTimer(0.5, function () use ($loop) {
$loop->stop();
});
$loop->run();

$this->assertEquals(2, $closed);
$this->assertTrue($process->isRunning());
}

public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
{
if (DIRECTORY_SEPARATOR === '\\') {
Expand All @@ -662,16 +781,42 @@ public function testDetectsClosingProcessEvenWhenAllStdioPipesHaveBeenClosed()
$this->assertSame(0, $process->getExitCode());
}

public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
/**
* @requires PHP 8
*/
public function testDetectsClosingProcessEvenWhenAllStdioSocketsHaveBeenClosed()
{
$loop = $this->createLoop();
$process = new Process(
(DIRECTORY_SEPARATOR === '\\' ? '' : 'exec ') . $this->getPhpBinary() . ' -r ' . escapeshellarg('fclose(STDIN);fclose(STDOUT);fclose(STDERR);usleep(10000);'),
null,
null,
array(
array('socket'),
array('socket'),
array('socket')
)
);
$process->start($loop, 0.001);

if (DIRECTORY_SEPARATOR === '\\') {
$process = new Process('cmd /c exit 0', null, null, array());
} else {
$process = new Process('exit 0', null, null, array());
}
$time = microtime(true);
$loop->run();
$time = microtime(true) - $time;

$this->assertLessThan(0.5, $time);
$this->assertSame(0, $process->getExitCode());
}

public function testDetectsClosingProcessEvenWhenStartedWithoutPipes()
{
$loop = $this->createLoop();

$process = new Process(
(DIRECTORY_SEPARATOR === '\\' ? 'cmd /c ' : '') . 'exit 0',
null,
null,
array()
);
$process->start($loop, 0.001);

$time = microtime(true);
Expand Down

0 comments on commit 74da6d5

Please sign in to comment.