Skip to content

Commit 41dcd7f

Browse files
committed
fix receiveFrom getting into endless loop when stream_select returns early but fread does not produce data and $lastAccess getting reset and moving timeout always into future (#73)
1 parent b170320 commit 41dcd7f

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/Network/StreamHandler.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ protected function receiveFrom(array $readStreams, float $timeout = null, $logge
6464
$streamIndex = $streamMap[$streamId] ?? null;
6565
if ($streamIndex !== null) {
6666
$data = fread($stream, 256); // read max 256 bytes
67+
if ($data === false) {
68+
throw new IOException('fread error during receiveFrom');
69+
}
6770
if (!empty($data)) {
6871
if ($logger) {
6972
$logger->debug("Stream {$streamId} @ index: {$streamIndex} received data: ", unpack('H*', $data));
@@ -89,8 +92,10 @@ protected function receiveFrom(array $readStreams, float $timeout = null, $logge
8992
if ($timeSpentWaiting >= $timeout) {
9093
throw new IOException('Read total timeout expired');
9194
}
95+
} else {
96+
$lastAccess = microtime(true);
9297
}
93-
$lastAccess = microtime(true);
98+
9499
}
95100
return $result;
96101
}

tests/unit/Network/StreamHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
class MockData
66
{
7+
// $loopForever makes mock return always same data from stream/fread. microtime returns still current time
8+
public static $loopForever = false;
9+
710
public static $fread = null;
811
public static $freadCounter = 0;
912

@@ -27,6 +30,10 @@ function fread($handle, $length)
2730
return \fread($handle, $length);
2831
}
2932

33+
if (MockData::$loopForever === true) {
34+
return MockData::$fread[0];
35+
}
36+
3037
return MockData::$fread[MockData::$freadCounter++];
3138
}
3239

@@ -40,6 +47,10 @@ function microtime($get_as_float = null)
4047
return \microtime($get_as_float);
4148
}
4249

50+
if (MockData::$loopForever === true) {
51+
return \microtime($get_as_float);
52+
}
53+
4354
return MockData::$microtime[MockData::$microtimeCounter++];
4455
}
4556

@@ -53,6 +64,10 @@ function stream_select(array &$read, array &$write, array &$except, $tv_sec, $tv
5364
return \stream_select($read, $write, $except, $tv_sec, $tv_usec);
5465
}
5566

67+
if (MockData::$loopForever === true) {
68+
return MockData::$stream_select[0];
69+
}
70+
5671
return MockData::$stream_select[MockData::$stream_selectCounter++];
5772
}
5873

@@ -69,6 +84,7 @@ class StreamHandlerTest extends TestCase
6984
{
7085
protected function tearDown(): void
7186
{
87+
MockData::$loopForever = false;
7288
MockData::$freadCounter = 0;
7389
MockData::$microtimeCounter = 0;
7490
MockData::$stream_selectCounter = 0;
@@ -102,6 +118,33 @@ public function testReturnData()
102118

103119
}
104120

121+
public function testSelectReturnButFreadFailure()
122+
{
123+
$this->expectExceptionMessage("fread error during receiveFrom");
124+
$this->expectException(IOException::class);
125+
126+
// full packet is received with 2 fread calls (tcp packet fragmentation example)
127+
$returnedPacketPart1 = "\xda\x87\x00\x00\x00\x03\x00\x81";
128+
$returnedPacketPart2 = "\x03";
129+
$expected = $returnedPacketPart1 . $returnedPacketPart2;
130+
131+
MockData::$loopForever = true;
132+
MockData::$fread = [false]; // fread() returns false - something is wrong with stream/socket
133+
MockData::$stream_select = ['x'];
134+
135+
$handler = new ForTestStreamHandler();
136+
137+
$readStreams = ['stream'];
138+
$timeout = null;
139+
$logger = new MockLogger();
140+
141+
$result = $handler->receiveFrom($readStreams, $timeout, $logger);
142+
143+
$this->assertEquals([$expected], $result);
144+
$this->assertEquals(2, MockData::$freadCounter);
145+
146+
}
147+
105148
public function testExceptionFromStreamSelect()
106149
{
107150
$this->expectExceptionMessage("stream_select interrupted by an incoming signal");

0 commit comments

Comments
 (0)