From 79db69974317d75a8ce016b57dbf605b587fbdb5 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 16 Mar 2026 10:16:10 +0100 Subject: [PATCH 1/9] Prevent blocking fread() There is an issue where this fread can block up to 5 seconds. Fix it by not issuing fread() if stream_select() say there is nothing to read. Encountered a strange issue on Ubuntu24.04 where stream_select() would incorrectly return 0 if we had never ran fread() on the socket, so made a workaround to always call fread the first time, even if socket_select() say there is nothing to read. - resolves https://github.com/chrome-php/chrome/issues/711 - resolves https://github.com/chrome-php/chrome/issues/646 - resolves https://github.com/chrome-php/chrome/issues/704 - probably resolves https://github.com/chrome-php/chrome/issues/710 --- src/Socket/AbstractSocket.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 91b0aee4..34fe25c2 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -56,6 +56,13 @@ abstract class AbstractSocket extends Configurable implements ResourceInterface */ protected $name; + /** + * Whether we have ran fread() on the socket at least once. + * + * @var bool + */ + private $hasBeenFreadInitialized = false; + /** * Gets the IP address of the socket. * @@ -242,9 +249,23 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string return $buffer; } - - $result = \fread($this->socket, $length); - + $readArray = [$this->socket]; + $writeArray = null; + $exceptArray = null; + $selectResult = @\stream_select($readArray, $writeArray, $exceptArray, 0); + if ( + // before the first fread(), stream_select() is unreliable (observed on Ubuntu24.04) + !$this->hasBeenFreadInitialized + // > 0 means there is data to read + || $selectResult > 0 + // false means we were unable to check if there is data to read, it does not mean there is no data to read. + || $selectResult === false + ) { + $result = \fread($this->socket, $length); + $this->hasBeenFreadInitialized = true; + } else { + $result = false; + } if ($makeBlockingAfterRead) { \stream_set_blocking($this->socket, true); $makeBlockingAfterRead = false; From 124c5c7b4264b2c3d3b947163873178ac42ebc29 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 16 Mar 2026 10:23:48 +0100 Subject: [PATCH 2/9] PHP8.4.12 Ubuntu24.04 --- src/Socket/AbstractSocket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 34fe25c2..05de32d4 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -254,7 +254,7 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string $exceptArray = null; $selectResult = @\stream_select($readArray, $writeArray, $exceptArray, 0); if ( - // before the first fread(), stream_select() is unreliable (observed on Ubuntu24.04) + // before the first fread(), stream_select() is unreliable (observed on PHP8.4.12 Ubuntu24.04) !$this->hasBeenFreadInitialized // > 0 means there is data to read || $selectResult > 0 From 0bc0ab53957b7c00418490460c166cd4293a38f3 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 16 Mar 2026 13:16:52 +0100 Subject: [PATCH 3/9] chrome-php/wrench1.8.0 --- src/Socket/AbstractSocket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 05de32d4..84bfda4d 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -254,7 +254,7 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string $exceptArray = null; $selectResult = @\stream_select($readArray, $writeArray, $exceptArray, 0); if ( - // before the first fread(), stream_select() is unreliable (observed on PHP8.4.12 Ubuntu24.04) + // before the first fread(), stream_select() is unreliable (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) !$this->hasBeenFreadInitialized // > 0 means there is data to read || $selectResult > 0 From 191456ece8b7cbc1cca92d0e2d7c2337cd22e568 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 16 Mar 2026 14:07:39 +0100 Subject: [PATCH 4/9] Update ClientTest.php --- tests/ClientTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 1e9fcb18..0e7bd904 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -116,7 +116,8 @@ public function testSend(): void $bytes = $instance->sendData('baz', Protocol::TYPE_TEXT); self::assertTrue($bytes >= 3, 'sent text frame'); - + self::assertSame([], $instance->receive(), 'Instantly recieve after send'); + \usleep(500000); // test fix for issue #43 $responses = $instance->receive(); self::assertTrue(\is_array($responses)); From 8d487c5f162d3a43457d58dcd656a4d1ebfb934b Mon Sep 17 00:00:00 2001 From: divinity76 Date: Mon, 16 Mar 2026 14:10:19 +0100 Subject: [PATCH 5/9] StyleCI --- src/Socket/AbstractSocket.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 84bfda4d..831a854d 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -58,7 +58,7 @@ abstract class AbstractSocket extends Configurable implements ResourceInterface /** * Whether we have ran fread() on the socket at least once. - * + * * @var bool */ private $hasBeenFreadInitialized = false; @@ -259,7 +259,7 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string // > 0 means there is data to read || $selectResult > 0 // false means we were unable to check if there is data to read, it does not mean there is no data to read. - || $selectResult === false + || false === $selectResult ) { $result = \fread($this->socket, $length); $this->hasBeenFreadInitialized = true; From d3fe73710db4bccd209737d845d9b96de99a2d31 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Tue, 17 Mar 2026 18:13:29 +0100 Subject: [PATCH 6/9] clarify --- src/Socket/AbstractSocket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 831a854d..c80b9652 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -254,7 +254,7 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string $exceptArray = null; $selectResult = @\stream_select($readArray, $writeArray, $exceptArray, 0); if ( - // before the first fread(), stream_select() is unreliable (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) + // before the first fread(), stream_select() may erroneously return 0 (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) !$this->hasBeenFreadInitialized // > 0 means there is data to read || $selectResult > 0 From 59278a38cdd660ab07c804afec9656deb1733ac6 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Wed, 18 Mar 2026 17:02:31 +0100 Subject: [PATCH 7/9] micro-optimize cus why not. --- src/Socket/AbstractSocket.php | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index c80b9652..902336cb 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -249,23 +249,24 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string return $buffer; } - $readArray = [$this->socket]; - $writeArray = null; - $exceptArray = null; - $selectResult = @\stream_select($readArray, $writeArray, $exceptArray, 0); - if ( - // before the first fread(), stream_select() may erroneously return 0 (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) - !$this->hasBeenFreadInitialized - // > 0 means there is data to read - || $selectResult > 0 - // false means we were unable to check if there is data to read, it does not mean there is no data to read. - || false === $selectResult - ) { - $result = \fread($this->socket, $length); + if (!$this->hasBeenFreadInitialized) { + // stream_select() may erroneously return 0 before the first fread() (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) + $this->hasBeenFreadInitialized = true; + $selectResult = 1; + } else { + $readArray = [$this->socket]; + $writeArray = null; + $exceptArray = null; + $selectResult = \stream_select($readArray, $writeArray, $exceptArray, 0); + } + // 1 means there is data to read, false means we were unable to check if there is data to read + if ($selectResult === 1 || $selectResult === false) { + $result = \fread($this->socket, $length); $this->hasBeenFreadInitialized = true; } else { $result = false; } + if ($makeBlockingAfterRead) { \stream_set_blocking($this->socket, true); $makeBlockingAfterRead = false; From 6698bc5abfe768c1fdcc986d4891ceebba83bc9a Mon Sep 17 00:00:00 2001 From: divinity76 Date: Wed, 18 Mar 2026 17:05:27 +0100 Subject: [PATCH 8/9] StyleCI --- src/Socket/AbstractSocket.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 902336cb..580475ad 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -252,16 +252,16 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string if (!$this->hasBeenFreadInitialized) { // stream_select() may erroneously return 0 before the first fread() (observed on PHP8.4.12 Ubuntu24.04 chrome-php/wrench1.8.0) $this->hasBeenFreadInitialized = true; - $selectResult = 1; + $selectResult = 1; } else { - $readArray = [$this->socket]; - $writeArray = null; - $exceptArray = null; + $readArray = [$this->socket]; + $writeArray = null; + $exceptArray = null; $selectResult = \stream_select($readArray, $writeArray, $exceptArray, 0); } // 1 means there is data to read, false means we were unable to check if there is data to read - if ($selectResult === 1 || $selectResult === false) { - $result = \fread($this->socket, $length); + if (1 === $selectResult || false === $selectResult) { + $result = \fread($this->socket, $length); $this->hasBeenFreadInitialized = true; } else { $result = false; From fe31e9cfdefb545105e3bc9ff3de1da155682645 Mon Sep 17 00:00:00 2001 From: divinity76 Date: Wed, 18 Mar 2026 17:07:56 +0100 Subject: [PATCH 9/9] nit --- src/Socket/AbstractSocket.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Socket/AbstractSocket.php b/src/Socket/AbstractSocket.php index 580475ad..fbeb8874 100644 --- a/src/Socket/AbstractSocket.php +++ b/src/Socket/AbstractSocket.php @@ -262,7 +262,6 @@ public function receive(int $length = self::DEFAULT_RECEIVE_LENGTH): string // 1 means there is data to read, false means we were unable to check if there is data to read if (1 === $selectResult || false === $selectResult) { $result = \fread($this->socket, $length); - $this->hasBeenFreadInitialized = true; } else { $result = false; }