diff --git a/src/Monolog/Handler/SocketHandler.php b/src/Monolog/Handler/SocketHandler.php index a3e7252e..2712c8e6 100644 --- a/src/Monolog/Handler/SocketHandler.php +++ b/src/Monolog/Handler/SocketHandler.php @@ -25,6 +25,8 @@ class SocketHandler extends AbstractProcessingHandler private $connectionTimeout; private $resource; private $timeout = 0; + private $writingTimeout = 0; + private $lastSentBytes = null; private $persistent = false; private $errno; private $errstr; @@ -113,6 +115,18 @@ class SocketHandler extends AbstractProcessingHandler $this->timeout = (float) $seconds; } + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + * + */ + public function setWritingTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->writingTimeout = (float) $seconds; + } + /** * Get current connection string * @@ -153,6 +167,16 @@ class SocketHandler extends AbstractProcessingHandler return $this->timeout; } + /** + * Get current local writing timeout + * + * @return float + */ + public function getWritingTimeout() + { + return $this->writingTimeout; + } + /** * Check to see if the socket is currently available. * @@ -262,6 +286,7 @@ class SocketHandler extends AbstractProcessingHandler { $length = strlen($data); $sent = 0; + $this->lastSentBytes = $sent; while ($this->isConnected() && $sent < $length) { if (0 == $sent) { $chunk = $this->fwrite($data); @@ -276,9 +301,35 @@ class SocketHandler extends AbstractProcessingHandler if ($socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } } if (!$this->isConnected() && $sent < $length) { throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); } } + + private function writingIsTimedOut($sent) + { + $writingTimeout = (int) floor($this->writingTimeout); + if (0 === $writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = time(); + $this->lastSentBytes = $sent; + } else { + usleep(100); + } + + if ((time() - $this->lastWritingAt) >= $writingTimeout) { + $this->closeSocket(); + return true; + } + + return false; + } } diff --git a/tests/Monolog/Handler/SocketHandlerTest.php b/tests/Monolog/Handler/SocketHandlerTest.php index 2e3d504a..69f08aa2 100644 --- a/tests/Monolog/Handler/SocketHandlerTest.php +++ b/tests/Monolog/Handler/SocketHandlerTest.php @@ -235,6 +235,26 @@ class SocketHandlerTest extends TestCase $this->assertTrue(is_resource($this->res)); } + /** + * @expectedException \RuntimeException + */ + public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $this->handler->expects($this->any()) + ->method('fwrite') + ->will($this->returnValue(0)); + + $this->handler->expects($this->any()) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->handler->setWritingTimeout(1); + + $this->writeRecord('Hello world'); + } + private function createHandler($connectionString) { $this->handler = new SocketHandler($connectionString);