Permalink
Browse files

adds ability to define an idle timeout

  • Loading branch information...
1 parent b788094 commit b922ba22e57d7557b2f7f09140fbf9756c134fed @schmittjoh schmittjoh committed Aug 2, 2013
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return $this->timeoutType === self::TYPE_GENERAL;
+ }
+
+ public function isIdleTimeout()
+ {
+ return $this->timeoutType === self::TYPE_IDLE;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
@@ -13,6 +13,7 @@
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
/**
@@ -44,7 +45,9 @@ class Process
private $env;
private $stdin;
private $starttime;
+ private $lastOutputTime;
private $timeout;
+ private $idleTimeout;
private $options;
private $exitcode;
private $fallbackExitcode;
@@ -231,7 +234,7 @@ public function start($callback = null)
throw new RuntimeException('Process is already running');
}
- $this->starttime = microtime(true);
+ $this->starttime = $this->lastOutputTime = microtime(true);
$this->stdout = '';
$this->stderr = '';
$this->incrementalOutputOffset = 0;
@@ -795,6 +798,7 @@ public function stop($timeout = 10, $signal = null)
*/
public function addOutput($line)
{
+ $this->lastOutputTime = microtime(true);
$this->stdout .= $line;
}
@@ -805,6 +809,7 @@ public function addOutput($line)
*/
public function addErrorOutput($line)
{
+ $this->lastOutputTime = microtime(true);
$this->stderr .= $line;
}
@@ -835,39 +840,53 @@ public function setCommandLine($commandline)
/**
* Gets the process timeout.
*
- * @return integer|null The timeout in seconds or null if it's disabled
+ * @return float|null The timeout in seconds or null if it's disabled
*/
public function getTimeout()
{
return $this->timeout;
}
/**
+ * Gets the process idle timeout.
+ *
+ * @return float|null
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
* Sets the process timeout.
*
* To disable the timeout, set this value to null.
*
- * @param float|null $timeout The timeout in seconds
+ * @param integer|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout($timeout)
{
- if (null === $timeout) {
- $this->timeout = null;
-
- return $this;
- }
-
- $timeout = (float) $timeout;
+ $this->timeout = $this->validateTimeout($timeout);
- if ($timeout < 0) {
- throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
- }
+ return $this;
+ }
- $this->timeout = $timeout;
+ /**
+ * Sets the process idle timeout.
+ *
+ * @param integer|float|null $timeout
+ *
+ * @return self The current Process instance.
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ $this->idleTimeout = $this->validateTimeout($timeout);
return $this;
}
@@ -1078,7 +1097,13 @@ public function checkTimeout()
if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
$this->stop(0);
- throw new RuntimeException('The process timed-out.');
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (0 < $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
}
}
@@ -1253,4 +1278,26 @@ private function hasSystemCallBeenInterrupted()
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
}
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @param integer|float|null $timeout
+ *
+ * @return float|null
+ */
+ private function validateTimeout($timeout)
+ {
+ if (null === $timeout) {
+ return null;
+ }
+
+ $timeout = (float) $timeout;
+
+ if ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
}
@@ -11,6 +11,7 @@
namespace Symfony\Component\Process\Tests;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
@@ -429,6 +430,45 @@ public function testCheckTimeoutOnStartedProcess()
$this->assertLessThan($timeout + $precision, $duration);
}
+ /**
+ * @group idle-timeout
+ */
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcess('sleep 3');
+ $process->setTimeout(10);
+ $process->setIdleTimeout(1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $ex) {
+ $this->assertTrue($ex->isIdleTimeout());
+ $this->assertFalse($ex->isGeneralTimeout());
+ $this->assertEquals(1.0, $ex->getExceededTimeout());
+ }
+ }
+
+ /**
+ * @group idle-timeout
+ */
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcess('echo "foo"; sleep 1; echo "foo"; sleep 1; echo "foo"; sleep 1; echo "foo"; sleep 5;');
+ $process->setTimeout(5);
+ $process->setIdleTimeout(3);
+
+ try {
+ $process->run();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $ex) {
+ $this->assertTrue($ex->isGeneralTimeout());
+ $this->assertFalse($ex->isIdleTimeout());
+ $this->assertEquals(5.0, $ex->getExceededTimeout());
+ }
+ }
+
public function testGetPid()
{
$process = $this->getProcess('php -r "sleep(1);"');

0 comments on commit b922ba2

Please sign in to comment.