Skip to content

Commit 7098c9e

Browse files
committed
Issue #24 Implement timeout feature
1 parent 290fc6c commit 7098c9e

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ $command->setStdIn('string');
9898
without making the process hang. The default is `null` which will enable
9999
the feature on Non-Windows systems. Set it to `true` or `false` to manually
100100
enable/disable it. Note that it doesn't work on Windows.
101+
* `$timeout`: The time in seconds after which the command should be
102+
terminated. This only works in non-blocking mode. Default is `null` which
103+
means the process is never terminated.
101104
* `$locale`: The locale to (temporarily) set with `setlocale()` before running the command.
102105
This can be set to e.g. `en_US.UTF-8` if you have issues with UTF-8 encoded arguments.
103106

src/Command.php

+27
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ class Command
6969
*/
7070
public $nonBlockingMode;
7171

72+
/**
73+
* @var int the time in seconds after which a command should be terminated.
74+
* This only works in non-blocking mode. Default is `null` which means the
75+
* process is never terminated.
76+
*/
77+
public $timeout;
78+
7279
/**
7380
* @var null|string the locale to temporarily set before calling
7481
* `escapeshellargs()`. Default is `null` for none.
@@ -376,6 +383,7 @@ public function execute()
376383
in_array(get_resource_type($this->_stdIn), array('file', 'stream'));
377384
$isInputString = is_string($this->_stdIn);
378385
$hasInput = $isInputStream || $isInputString;
386+
$hasTimeout = $this->timeout !== null && $this->timeout > 0;
379387

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

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

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

394404
if (is_resource($process)) {
@@ -457,8 +467,25 @@ public function execute()
457467
$this->_stdErr .= $err;
458468
}
459469

470+
$runTime = $hasTimeout ? time() - $startTime : 0;
471+
if ($isRunning && $hasTimeout && $runTime >= $this->timeout) {
472+
// Only send a SIGTERM and handle status in the next cycle
473+
proc_terminate($process);
474+
}
475+
460476
if (!$isRunning) {
461477
$this->_exitCode = $status['exitcode'];
478+
if ($this->_exitCode !== 0 && empty($this->_stdErr)) {
479+
if ($status['stopped']) {
480+
$signal = $status['stopsig'];
481+
$this->_stdErr = "Command stopped by signal $signal";
482+
} elseif ($status['signaled']) {
483+
$signal = $status['termsig'];
484+
$this->_stdErr = "Command terminated by signal $signal";
485+
} else {
486+
$this->_stdErr = 'Command unexpectedly terminated without error message';
487+
}
488+
}
462489
fclose($pipes[1]);
463490
fclose($pipes[2]);
464491
proc_close($process);

tests/CommandTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,19 @@ public function testCanRunLongRunningCommandWithStandardInputStream()
280280
$this->assertEquals(strlen($expected), strlen($command->getOutput()));
281281
fclose($tmpfile);
282282
}
283+
284+
public function testCanTerminateLongRunningCommandWithTimeout()
285+
{
286+
$command = new Command('sleep 5');
287+
$command->timeout = 2;
288+
$startTime = time();
289+
$this->assertFalse($command->execute());
290+
$stopTime = time();
291+
$this->assertFalse($command->getExecuted());
292+
$this->assertNotEquals(0, $command->getExitCode());
293+
$this->assertStringStartsWith('Command terminated by signal', $command->getError());
294+
$this->assertStringStartsWith('Command terminated by signal', $command->getStdErr());
295+
$this->assertEmpty($command->getOutput());
296+
$this->assertEquals(2, $stopTime - $startTime);
297+
}
283298
}

0 commit comments

Comments
 (0)