mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-01 10:50:21 +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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user