1
0
mirror of https://github.com/Seldaek/monolog.git synced 2025-08-05 20:57:36 +02:00

Avoid infinite loops when no data is written on a socket for a time greater than writingTimeout settings

This commit is contained in:
Danilo Silva
2015-10-19 12:02:32 +02:00
parent fa74e171dc
commit 5c129a7f7f
2 changed files with 71 additions and 0 deletions

View File

@@ -25,6 +25,8 @@ class SocketHandler extends AbstractProcessingHandler
private $connectionTimeout; private $connectionTimeout;
private $resource; private $resource;
private $timeout = 0; private $timeout = 0;
private $writingTimeout = 0;
private $lastSentBytes = null;
private $persistent = false; private $persistent = false;
private $errno; private $errno;
private $errstr; private $errstr;
@@ -113,6 +115,18 @@ class SocketHandler extends AbstractProcessingHandler
$this->timeout = (float) $seconds; $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 * Get current connection string
* *
@@ -153,6 +167,16 @@ class SocketHandler extends AbstractProcessingHandler
return $this->timeout; 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. * Check to see if the socket is currently available.
* *
@@ -262,6 +286,7 @@ class SocketHandler extends AbstractProcessingHandler
{ {
$length = strlen($data); $length = strlen($data);
$sent = 0; $sent = 0;
$this->lastSentBytes = $sent;
while ($this->isConnected() && $sent < $length) { while ($this->isConnected() && $sent < $length) {
if (0 == $sent) { if (0 == $sent) {
$chunk = $this->fwrite($data); $chunk = $this->fwrite($data);
@@ -276,9 +301,35 @@ class SocketHandler extends AbstractProcessingHandler
if ($socketInfo['timed_out']) { if ($socketInfo['timed_out']) {
throw new \RuntimeException("Write 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) { if (!$this->isConnected() && $sent < $length) {
throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $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;
}
} }

View File

@@ -235,6 +235,26 @@ class SocketHandlerTest extends TestCase
$this->assertTrue(is_resource($this->res)); $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) private function createHandler($connectionString)
{ {
$this->handler = new SocketHandler($connectionString); $this->handler = new SocketHandler($connectionString);