Skip to content

Commit

Permalink
Merge pull request #42 from mikehaertl/24-implement-timeout
Browse files Browse the repository at this point in the history
Implement timeout feature
  • Loading branch information
mikehaertl committed Sep 15, 2019
2 parents 290fc6c + 7098c9e commit 6c6f44c
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ $command->setStdIn('string');
without making the process hang. The default is `null` which will enable
the feature on Non-Windows systems. Set it to `true` or `false` to manually
enable/disable it. Note that it doesn't work on Windows.
* `$timeout`: The time in seconds after which the command should be
terminated. This only works in non-blocking mode. Default is `null` which
means the process is never terminated.
* `$locale`: The locale to (temporarily) set with `setlocale()` before running the command.
This can be set to e.g. `en_US.UTF-8` if you have issues with UTF-8 encoded arguments.

Expand Down
27 changes: 27 additions & 0 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class Command
*/
public $nonBlockingMode;

/**
* @var int the time in seconds after which a command should be terminated.
* This only works in non-blocking mode. Default is `null` which means the
* process is never terminated.
*/
public $timeout;

/**
* @var null|string the locale to temporarily set before calling
* `escapeshellargs()`. Default is `null` for none.
Expand Down Expand Up @@ -376,6 +383,7 @@ public function execute()
in_array(get_resource_type($this->_stdIn), array('file', 'stream'));
$isInputString = is_string($this->_stdIn);
$hasInput = $isInputStream || $isInputString;
$hasTimeout = $this->timeout !== null && $this->timeout > 0;

$descriptors = array(
1 => array('pipe','w'),
Expand All @@ -385,10 +393,12 @@ public function execute()
$descriptors[0] = array('pipe', 'r');
}


// Issue #20 Set non-blocking mode to fix hanging processes
$nonBlocking = $this->nonBlockingMode === null ?
!$this->getIsWindows() : $this->nonBlockingMode;

$startTime = $hasTimeout ? time() : 0;
$process = proc_open($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions);

if (is_resource($process)) {
Expand Down Expand Up @@ -457,8 +467,25 @@ public function execute()
$this->_stdErr .= $err;
}

$runTime = $hasTimeout ? time() - $startTime : 0;
if ($isRunning && $hasTimeout && $runTime >= $this->timeout) {
// Only send a SIGTERM and handle status in the next cycle
proc_terminate($process);
}

if (!$isRunning) {
$this->_exitCode = $status['exitcode'];
if ($this->_exitCode !== 0 && empty($this->_stdErr)) {
if ($status['stopped']) {
$signal = $status['stopsig'];
$this->_stdErr = "Command stopped by signal $signal";
} elseif ($status['signaled']) {
$signal = $status['termsig'];
$this->_stdErr = "Command terminated by signal $signal";
} else {
$this->_stdErr = 'Command unexpectedly terminated without error message';
}
}
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
Expand Down
15 changes: 15 additions & 0 deletions tests/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,19 @@ public function testCanRunLongRunningCommandWithStandardInputStream()
$this->assertEquals(strlen($expected), strlen($command->getOutput()));
fclose($tmpfile);
}

public function testCanTerminateLongRunningCommandWithTimeout()
{
$command = new Command('sleep 5');
$command->timeout = 2;
$startTime = time();
$this->assertFalse($command->execute());
$stopTime = time();
$this->assertFalse($command->getExecuted());
$this->assertNotEquals(0, $command->getExitCode());
$this->assertStringStartsWith('Command terminated by signal', $command->getError());
$this->assertStringStartsWith('Command terminated by signal', $command->getStdErr());
$this->assertEmpty($command->getOutput());
$this->assertEquals(2, $stopTime - $startTime);
}
}

0 comments on commit 6c6f44c

Please sign in to comment.