From 8b4d1c326aca2bda98ad5b287dc9250a7c707307 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 7 Oct 2025 19:16:38 -0400 Subject: [PATCH 01/21] Handle socket not ready --- src/Client.php | 83 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/Client.php b/src/Client.php index a9c5ea8..0ce2953 100644 --- a/src/Client.php +++ b/src/Client.php @@ -554,25 +554,39 @@ public function call($method, $headers = [], $body = '') */ private function sendRawRequest(string $host, int $port, string $rawRequest) { - // Try to connect to the requested host $pid = getmypid(); if (!$this->socket) { $this->logger->info('Bedrock\Client - Opening new socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); $this->socket = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); - - // Make sure we succeed to create a socket if ($this->socket === false) { $socketError = socket_strerror(socket_last_error()); throw new ConnectionFailure("Could not connect to create socket: $socketError"); } - - // Configure this socket and try to connect to it + socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); + @socket_connect($this->socket, $host, $port); $socketErrorCode = socket_last_error($this->socket); - if ($socketErrorCode === 115) { - $this->logger->info('Bedrock\Client - socket_connect returned error 115, continuing.'); + + if ($socketErrorCode === 115 /* EINPROGRESS */) { + // Wait for connect to complete (socket becomes writable), then check SO_ERROR + $write = [$this->socket]; + $read = $except = []; + $sec = $this->connectionTimeout; + $usec = $this->connectionTimeoutMicroseconds; + $sel = @socket_select($read, $write, $except, $sec, $usec); + if ($sel === 1) { + $soError = 0; + $len = 4; // int + @socket_get_option($this->socket, SOL_SOCKET, SO_ERROR, $soError); + if (!empty($soError)) { + $errStr = socket_strerror($soError); + throw new ConnectionFailure("Connect completion failed to $host:$port. Error: $soError $errStr"); + } + } else { + throw new ConnectionFailure("Connect to $host:$port timed out waiting for completion"); + } } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); @@ -580,26 +594,43 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) } else { $this->logger->info('Bedrock\Client - Reusing socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); } + socket_clear_error($this->socket); - - // Send the information to the socket - $bytesSent = @socket_send($this->socket, $rawRequest, strlen($rawRequest), MSG_EOF); - - // Failed to send anything - if ($bytesSent === false) { - $socketErrorCode = socket_last_error(); - $socketError = socket_strerror($socketErrorCode); - throw new ConnectionFailure("Failed to send request to bedrock host $host:$port. Error: $socketErrorCode $socketError"); - } - - // We sent something; can't retry or else we might double-send the same request. Let's make sure we sent the - // whole thing, else there's a problem. - if ($bytesSent < strlen($rawRequest)) { - $this->logger->info('Bedrock\Client - Could not send the whole request', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]); - throw new ConnectionFailure("Sent partial request to bedrock host $host:$port"); - } elseif ($bytesSent > strlen($rawRequest)) { - $this->logger->info('Bedrock\Client - sent more data than needed', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]); - throw new BedrockError("Sent more content than expected to host $host:$port"); + + // --- robust send with EAGAIN handling --- + $total = strlen($rawRequest); + $sent = 0; + while ($sent < $total) { + $chunk = substr($rawRequest, $sent); + $bytesSent = @socket_send($this->socket, $chunk, strlen($chunk), MSG_EOF); + + if ($bytesSent === false) { + $code = socket_last_error($this->socket); + if ($code === 11 /* EAGAIN/EWOULDBLOCK */) { + // Wait until writable, then retry + $write = [$this->socket]; + $read = $except = []; + $sec = $this->connectionTimeout; + $usec = $this->connectionTimeoutMicroseconds; + $sel = @socket_select($read, $write, $except, $sec, $usec); + if ($sel === 1) { + continue; // try send again + } + throw new ConnectionFailure("Send to $host:$port timed out waiting for socket writable"); + } + $err = socket_strerror($code); + throw new ConnectionFailure("Failed to send request to bedrock host $host:$port. Error: $code $err"); + } + + if ($bytesSent === 0) { + // treat like would-block; wait once then retry + $write = [$this->socket]; + $read = $except = []; + @socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); + continue; + } + + $sent += $bytesSent; } } From 70d13976da274bcab9b2557b6bc39f4cca742bfd Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 14:54:03 -0400 Subject: [PATCH 02/21] Add socket health check to prevent EAGAIN errors from stale connections - Add isSocketHealthy() method that checks if a socket is still usable by peeking at the socket state - Detects when remote end has closed the connection - Detects other socket errors that would cause issues on reuse - Modify sendRawRequest() to validate socket health before attempting reuse - Automatically closes stale sockets and opens new connections - This should significantly reduce 'Error 11: Resource temporarily unavailable' errors --- src/Client.php | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/Client.php b/src/Client.php index 0ce2953..1fa1dcb 100644 --- a/src/Client.php +++ b/src/Client.php @@ -546,6 +546,54 @@ public function call($method, $headers = [], $body = '') return $response; } + /** + * Checks if the current socket is still healthy and usable for sending data. + * + * @return bool True if socket is healthy, false if it's stale or closed + */ + private function isSocketHealthy(): bool + { + if (!$this->socket) { + return false; + } + + // Check if the remote end has closed the connection by peeking at the socket + $buf = ''; + $result = @socket_recv($this->socket, $buf, 1, MSG_PEEK | MSG_DONTWAIT); + + // If recv returns 0, the connection has been closed by the remote end + if ($result === 0) { + $this->logger->info('Bedrock\Client - Socket detected as closed by remote end', [ + 'host' => $this->lastHost, + 'cluster' => $this->clusterName, + ]); + return false; + } + + // If recv returned false, check the error code + if ($result === false) { + $errorCode = socket_last_error($this->socket); + + // EAGAIN/EWOULDBLOCK (11) is expected when no data is available but socket is healthy + if ($errorCode === 11 /* EAGAIN/EWOULDBLOCK */) { + socket_clear_error($this->socket); + return true; + } + + // Any other error means the socket is not healthy + $this->logger->info('Bedrock\Client - Socket detected as unhealthy', [ + 'host' => $this->lastHost, + 'cluster' => $this->clusterName, + 'errorCode' => $errorCode, + 'error' => socket_strerror($errorCode), + ]); + return false; + } + + // Socket has data available or is healthy + return true; + } + /** * Sends the request on a new socket, if a previous one existed, it closes the connection first. * @@ -592,6 +640,21 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); } } else { + // Check if the existing socket is still healthy before reusing it + if (!$this->isSocketHealthy()) { + $this->logger->info('Bedrock\Client - Existing socket is stale, closing and reconnecting', [ + 'host' => $host, + 'cluster' => $this->clusterName, + 'pid' => $pid, + ]); + + @socket_close($this->socket); + $this->socket = null; + + // Recursively call to open a new socket + return $this->sendRawRequest($host, $port, $rawRequest); + } + $this->logger->info('Bedrock\Client - Reusing socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); } From de1515e1c4723df3f7b53d37f83125b76d212e24 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 14:56:08 -0400 Subject: [PATCH 03/21] Add enhanced socket metrics logging for better diagnostics - Track socket creation time, last used time, and request count - Log socket age (seconds since creation) when reusing or detecting stale sockets - Log socket idle time (seconds since last use) for better debugging - Log number of requests made on each socket connection - Reset metrics when socket is closed (on failure, Connection: close, or stale detection) - Provides visibility into socket lifecycle and helps identify patterns in EAGAIN errors --- src/Client.php | 60 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 1fa1dcb..2ddc464 100644 --- a/src/Client.php +++ b/src/Client.php @@ -150,6 +150,21 @@ class Client implements LoggerAwareInterface */ private $logParam; + /** + * @var float|null Timestamp (from microtime(true)) when the current socket was opened + */ + private $socketOpenTime = null; + + /** + * @var float|null Timestamp (from microtime(true)) when the socket was last used for a request + */ + private $socketLastUsedTime = null; + + /** + * @var int Number of requests made on the current socket + */ + private $socketRequestCount = 0; + /** * Creates a reusable Bedrock instance. * All params are optional and values set in `configure` would be used if are not passed here. @@ -523,6 +538,9 @@ public function call($method, $headers = [], $body = '') $this->logger->info('Closing socket after use'); @socket_close($this->socket); $this->socket = null; + $this->socketOpenTime = null; + $this->socketLastUsedTime = null; + $this->socketRequestCount = 0; } // Log how long this particular call took @@ -557,6 +575,11 @@ private function isSocketHealthy(): bool return false; } + // Calculate socket metrics for logging + $now = microtime(true); + $socketAgeSeconds = $this->socketOpenTime ? round($now - $this->socketOpenTime, 3) : null; + $socketIdleSeconds = $this->socketLastUsedTime ? round($now - $this->socketLastUsedTime, 3) : null; + // Check if the remote end has closed the connection by peeking at the socket $buf = ''; $result = @socket_recv($this->socket, $buf, 1, MSG_PEEK | MSG_DONTWAIT); @@ -566,6 +589,9 @@ private function isSocketHealthy(): bool $this->logger->info('Bedrock\Client - Socket detected as closed by remote end', [ 'host' => $this->lastHost, 'cluster' => $this->clusterName, + 'socketAgeSeconds' => $socketAgeSeconds, + 'socketIdleSeconds' => $socketIdleSeconds, + 'requestsOnSocket' => $this->socketRequestCount, ]); return false; } @@ -586,6 +612,9 @@ private function isSocketHealthy(): bool 'cluster' => $this->clusterName, 'errorCode' => $errorCode, 'error' => socket_strerror($errorCode), + 'socketAgeSeconds' => $socketAgeSeconds, + 'socketIdleSeconds' => $socketIdleSeconds, + 'requestsOnSocket' => $this->socketRequestCount, ]); return false; } @@ -610,6 +639,10 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $socketError = socket_strerror(socket_last_error()); throw new ConnectionFailure("Could not connect to create socket: $socketError"); } + + // Track socket creation time + $this->socketOpenTime = microtime(true); + $this->socketRequestCount = 0; socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); @@ -640,22 +673,40 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); } } else { + // Calculate socket metrics + $now = microtime(true); + $socketAgeSeconds = $this->socketOpenTime ? round($now - $this->socketOpenTime, 3) : null; + $socketIdleSeconds = $this->socketLastUsedTime ? round($now - $this->socketLastUsedTime, 3) : null; + // Check if the existing socket is still healthy before reusing it if (!$this->isSocketHealthy()) { $this->logger->info('Bedrock\Client - Existing socket is stale, closing and reconnecting', [ 'host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid, + 'socketAgeSeconds' => $socketAgeSeconds, + 'socketIdleSeconds' => $socketIdleSeconds, + 'requestsOnSocket' => $this->socketRequestCount, ]); @socket_close($this->socket); $this->socket = null; + $this->socketOpenTime = null; + $this->socketLastUsedTime = null; + $this->socketRequestCount = 0; // Recursively call to open a new socket return $this->sendRawRequest($host, $port, $rawRequest); } - $this->logger->info('Bedrock\Client - Reusing socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); + $this->logger->info('Bedrock\Client - Reusing socket', [ + 'host' => $host, + 'cluster' => $this->clusterName, + 'pid' => $pid, + 'socketAgeSeconds' => $socketAgeSeconds, + 'socketIdleSeconds' => $socketIdleSeconds, + 'requestsOnSocket' => $this->socketRequestCount, + ]); } socket_clear_error($this->socket); @@ -695,6 +746,10 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $sent += $bytesSent; } + + // Update socket usage metrics after successful send + $this->socketLastUsedTime = microtime(true); + $this->socketRequestCount++; } /** @@ -949,6 +1004,9 @@ private function markHostAsFailed(string $host) if ($this->socket) { @socket_close($this->socket); $this->socket = null; + $this->socketOpenTime = null; + $this->socketLastUsedTime = null; + $this->socketRequestCount = 0; } } From 58af4654caaf0ee18bebf3d00be5c21ff1e17c20 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 15:27:19 -0400 Subject: [PATCH 04/21] Revert "Handle socket not ready" This reverts commit 8b4d1c326aca2bda98ad5b287dc9250a7c707307. --- src/Client.php | 80 +++++++++++++++----------------------------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/src/Client.php b/src/Client.php index 2ddc464..aba1b7b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -631,10 +631,13 @@ private function isSocketHealthy(): bool */ private function sendRawRequest(string $host, int $port, string $rawRequest) { + // Try to connect to the requested host $pid = getmypid(); if (!$this->socket) { $this->logger->info('Bedrock\Client - Opening new socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); $this->socket = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); + + // Make sure we succeed to create a socket if ($this->socket === false) { $socketError = socket_strerror(socket_last_error()); throw new ConnectionFailure("Could not connect to create socket: $socketError"); @@ -646,28 +649,10 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); - @socket_connect($this->socket, $host, $port); $socketErrorCode = socket_last_error($this->socket); - - if ($socketErrorCode === 115 /* EINPROGRESS */) { - // Wait for connect to complete (socket becomes writable), then check SO_ERROR - $write = [$this->socket]; - $read = $except = []; - $sec = $this->connectionTimeout; - $usec = $this->connectionTimeoutMicroseconds; - $sel = @socket_select($read, $write, $except, $sec, $usec); - if ($sel === 1) { - $soError = 0; - $len = 4; // int - @socket_get_option($this->socket, SOL_SOCKET, SO_ERROR, $soError); - if (!empty($soError)) { - $errStr = socket_strerror($soError); - throw new ConnectionFailure("Connect completion failed to $host:$port. Error: $soError $errStr"); - } - } else { - throw new ConnectionFailure("Connect to $host:$port timed out waiting for completion"); - } + if ($socketErrorCode === 115) { + $this->logger->info('Bedrock\Client - socket_connect returned error 115, continuing.'); } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); @@ -708,43 +693,26 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) 'requestsOnSocket' => $this->socketRequestCount, ]); } - socket_clear_error($this->socket); - - // --- robust send with EAGAIN handling --- - $total = strlen($rawRequest); - $sent = 0; - while ($sent < $total) { - $chunk = substr($rawRequest, $sent); - $bytesSent = @socket_send($this->socket, $chunk, strlen($chunk), MSG_EOF); - - if ($bytesSent === false) { - $code = socket_last_error($this->socket); - if ($code === 11 /* EAGAIN/EWOULDBLOCK */) { - // Wait until writable, then retry - $write = [$this->socket]; - $read = $except = []; - $sec = $this->connectionTimeout; - $usec = $this->connectionTimeoutMicroseconds; - $sel = @socket_select($read, $write, $except, $sec, $usec); - if ($sel === 1) { - continue; // try send again - } - throw new ConnectionFailure("Send to $host:$port timed out waiting for socket writable"); - } - $err = socket_strerror($code); - throw new ConnectionFailure("Failed to send request to bedrock host $host:$port. Error: $code $err"); - } - - if ($bytesSent === 0) { - // treat like would-block; wait once then retry - $write = [$this->socket]; - $read = $except = []; - @socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); - continue; - } - - $sent += $bytesSent; + + // Send the information to the socket + $bytesSent = @socket_send($this->socket, $rawRequest, strlen($rawRequest), MSG_EOF); + + // Failed to send anything + if ($bytesSent === false) { + $socketErrorCode = socket_last_error(); + $socketError = socket_strerror($socketErrorCode); + throw new ConnectionFailure("Failed to send request to bedrock host $host:$port. Error: $socketErrorCode $socketError"); + } + + // We sent something; can't retry or else we might double-send the same request. Let's make sure we sent the + // whole thing, else there's a problem. + if ($bytesSent < strlen($rawRequest)) { + $this->logger->info('Bedrock\Client - Could not send the whole request', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]); + throw new ConnectionFailure("Sent partial request to bedrock host $host:$port"); + } elseif ($bytesSent > strlen($rawRequest)) { + $this->logger->info('Bedrock\Client - sent more data than needed', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]); + throw new BedrockError("Sent more content than expected to host $host:$port"); } // Update socket usage metrics after successful send From 113ed7d9c898ab95fcbf36ee563d04173a0d5963 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 19:48:59 -0400 Subject: [PATCH 05/21] Fix PHP 8 socket EINPROGRESS handling and EAGAIN errors - Set socket to non-blocking mode immediately after creation for proper PHP 8 Socket object behavior - Add proper socket_select() wait after EINPROGRESS to ensure socket is ready for writing - Increase connection timeout from 1s to 5s for remote datacenter connections - Add detailed timing logs to track connection establishment performance - Return socket to blocking mode after connection established for normal I/O operations This addresses intermittent 'Resource temporarily unavailable' errors that occur when connecting to remote Bedrock servers, particularly in PHP 8 environments where Socket objects have different timing behavior than legacy resources --- src/Client.php | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Client.php b/src/Client.php index aba1b7b..fee89c8 100644 --- a/src/Client.php +++ b/src/Client.php @@ -269,7 +269,7 @@ public static function configure(array $config) 'clusterName' => 'bedrock', 'mainHostConfigs' => ['localhost' => ['blacklistedUntil' => 0, 'port' => 8888]], 'failoverHostConfigs' => ['localhost' => ['blacklistedUntil' => 0, 'port' => 8888]], - 'connectionTimeout' => 1, + 'connectionTimeout' => 5, 'connectionTimeoutMicroseconds' => 0, 'readTimeout' => 120, 'readTimeoutMicroseconds' => 0, @@ -643,16 +643,54 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to create socket: $socketError"); } + // Set socket to non-blocking mode IMMEDIATELY after creation (PHP 8 compatibility) + socket_set_nonblock($this->socket); + // Track socket creation time $this->socketOpenTime = microtime(true); $this->socketRequestCount = 0; - + socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); + $connectStart = microtime(true); @socket_connect($this->socket, $host, $port); + $connectTime = (microtime(true) - $connectStart) * 1000; // Convert to milliseconds $socketErrorCode = socket_last_error($this->socket); if ($socketErrorCode === 115) { - $this->logger->info('Bedrock\Client - socket_connect returned error 115, continuing.'); + $this->logger->info('EINPROGRESS_WAIT: socket_connect returned error 115, waiting for connection to complete.', [ + 'host' => $host, + 'connect_attempt_time_ms' => round($connectTime, 3), + 'pid' => getmypid() + ]); + + // Wait for the socket to be ready for writing after EINPROGRESS + $write = [$this->socket]; + $read = []; + $except = []; + $selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); + + if ($selectResult === false) { + $socketError = socket_strerror(socket_last_error($this->socket)); + throw new ConnectionFailure("socket_select failed after EINPROGRESS for $host:$port. Error: $socketError"); + } elseif ($selectResult === 0) { + throw new ConnectionFailure("Socket not ready for writing within timeout after EINPROGRESS for $host:$port"); + } elseif (empty($write)) { + $socketErrorCode = socket_last_error($this->socket); + $socketError = socket_strerror($socketErrorCode); + throw new ConnectionFailure("Socket had error after EINPROGRESS for $host:$port. Error: $socketErrorCode $socketError"); + } + + $selectTime = (microtime(true) - $connectStart) * 1000; // Total time from connect to ready + + // Set socket back to blocking mode for normal operations + socket_set_block($this->socket); + + $this->logger->info('EINPROGRESS_SUCCESS: Socket ready for writing after EINPROGRESS.', [ + 'host' => $host, + 'total_connection_time_ms' => round($selectTime, 3), + 'select_wait_time_ms' => round($selectTime - $connectTime, 3), + 'pid' => getmypid() + ]); } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); From 348570a5fa103504ea0952f50fb39de8988cd584 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 19:50:59 -0400 Subject: [PATCH 06/21] revert timeout --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index fee89c8..d686bfc 100644 --- a/src/Client.php +++ b/src/Client.php @@ -269,7 +269,7 @@ public static function configure(array $config) 'clusterName' => 'bedrock', 'mainHostConfigs' => ['localhost' => ['blacklistedUntil' => 0, 'port' => 8888]], 'failoverHostConfigs' => ['localhost' => ['blacklistedUntil' => 0, 'port' => 8888]], - 'connectionTimeout' => 5, + 'connectionTimeout' => 1, 'connectionTimeoutMicroseconds' => 0, 'readTimeout' => 120, 'readTimeoutMicroseconds' => 0, From 3d6e486502682342555a038ea9e3a6e227cdedb8 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 19:53:29 -0400 Subject: [PATCH 07/21] remove isSocketHealthy --- src/Client.php | 93 -------------------------------------------------- 1 file changed, 93 deletions(-) diff --git a/src/Client.php b/src/Client.php index d686bfc..91257b6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -564,64 +564,6 @@ public function call($method, $headers = [], $body = '') return $response; } - /** - * Checks if the current socket is still healthy and usable for sending data. - * - * @return bool True if socket is healthy, false if it's stale or closed - */ - private function isSocketHealthy(): bool - { - if (!$this->socket) { - return false; - } - - // Calculate socket metrics for logging - $now = microtime(true); - $socketAgeSeconds = $this->socketOpenTime ? round($now - $this->socketOpenTime, 3) : null; - $socketIdleSeconds = $this->socketLastUsedTime ? round($now - $this->socketLastUsedTime, 3) : null; - - // Check if the remote end has closed the connection by peeking at the socket - $buf = ''; - $result = @socket_recv($this->socket, $buf, 1, MSG_PEEK | MSG_DONTWAIT); - - // If recv returns 0, the connection has been closed by the remote end - if ($result === 0) { - $this->logger->info('Bedrock\Client - Socket detected as closed by remote end', [ - 'host' => $this->lastHost, - 'cluster' => $this->clusterName, - 'socketAgeSeconds' => $socketAgeSeconds, - 'socketIdleSeconds' => $socketIdleSeconds, - 'requestsOnSocket' => $this->socketRequestCount, - ]); - return false; - } - - // If recv returned false, check the error code - if ($result === false) { - $errorCode = socket_last_error($this->socket); - - // EAGAIN/EWOULDBLOCK (11) is expected when no data is available but socket is healthy - if ($errorCode === 11 /* EAGAIN/EWOULDBLOCK */) { - socket_clear_error($this->socket); - return true; - } - - // Any other error means the socket is not healthy - $this->logger->info('Bedrock\Client - Socket detected as unhealthy', [ - 'host' => $this->lastHost, - 'cluster' => $this->clusterName, - 'errorCode' => $errorCode, - 'error' => socket_strerror($errorCode), - 'socketAgeSeconds' => $socketAgeSeconds, - 'socketIdleSeconds' => $socketIdleSeconds, - 'requestsOnSocket' => $this->socketRequestCount, - ]); - return false; - } - - // Socket has data available or is healthy - return true; - } /** * Sends the request on a new socket, if a previous one existed, it closes the connection first. @@ -695,41 +637,6 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); } - } else { - // Calculate socket metrics - $now = microtime(true); - $socketAgeSeconds = $this->socketOpenTime ? round($now - $this->socketOpenTime, 3) : null; - $socketIdleSeconds = $this->socketLastUsedTime ? round($now - $this->socketLastUsedTime, 3) : null; - - // Check if the existing socket is still healthy before reusing it - if (!$this->isSocketHealthy()) { - $this->logger->info('Bedrock\Client - Existing socket is stale, closing and reconnecting', [ - 'host' => $host, - 'cluster' => $this->clusterName, - 'pid' => $pid, - 'socketAgeSeconds' => $socketAgeSeconds, - 'socketIdleSeconds' => $socketIdleSeconds, - 'requestsOnSocket' => $this->socketRequestCount, - ]); - - @socket_close($this->socket); - $this->socket = null; - $this->socketOpenTime = null; - $this->socketLastUsedTime = null; - $this->socketRequestCount = 0; - - // Recursively call to open a new socket - return $this->sendRawRequest($host, $port, $rawRequest); - } - - $this->logger->info('Bedrock\Client - Reusing socket', [ - 'host' => $host, - 'cluster' => $this->clusterName, - 'pid' => $pid, - 'socketAgeSeconds' => $socketAgeSeconds, - 'socketIdleSeconds' => $socketIdleSeconds, - 'requestsOnSocket' => $this->socketRequestCount, - ]); } socket_clear_error($this->socket); From a8c485acd6bb4ea0a085b0f75956912f4609c44d Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 19:57:27 -0400 Subject: [PATCH 08/21] cleanup --- src/Client.php | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/Client.php b/src/Client.php index 91257b6..a286dd9 100644 --- a/src/Client.php +++ b/src/Client.php @@ -150,20 +150,6 @@ class Client implements LoggerAwareInterface */ private $logParam; - /** - * @var float|null Timestamp (from microtime(true)) when the current socket was opened - */ - private $socketOpenTime = null; - - /** - * @var float|null Timestamp (from microtime(true)) when the socket was last used for a request - */ - private $socketLastUsedTime = null; - - /** - * @var int Number of requests made on the current socket - */ - private $socketRequestCount = 0; /** * Creates a reusable Bedrock instance. @@ -538,9 +524,6 @@ public function call($method, $headers = [], $body = '') $this->logger->info('Closing socket after use'); @socket_close($this->socket); $this->socket = null; - $this->socketOpenTime = null; - $this->socketLastUsedTime = null; - $this->socketRequestCount = 0; } // Log how long this particular call took @@ -588,9 +571,6 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) // Set socket to non-blocking mode IMMEDIATELY after creation (PHP 8 compatibility) socket_set_nonblock($this->socket); - // Track socket creation time - $this->socketOpenTime = microtime(true); - $this->socketRequestCount = 0; socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); @@ -599,7 +579,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $connectTime = (microtime(true) - $connectStart) * 1000; // Convert to milliseconds $socketErrorCode = socket_last_error($this->socket); if ($socketErrorCode === 115) { - $this->logger->info('EINPROGRESS_WAIT: socket_connect returned error 115, waiting for connection to complete.', [ + $this->logger->info('Bedrock\Client - socket_connect returned error 115, waiting for connection to complete.', [ 'host' => $host, 'connect_attempt_time_ms' => round($connectTime, 3), 'pid' => getmypid() @@ -627,7 +607,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) // Set socket back to blocking mode for normal operations socket_set_block($this->socket); - $this->logger->info('EINPROGRESS_SUCCESS: Socket ready for writing after EINPROGRESS.', [ + $this->logger->info('Bedrock\Client - Socket ready for writing after EINPROGRESS.', [ 'host' => $host, 'total_connection_time_ms' => round($selectTime, 3), 'select_wait_time_ms' => round($selectTime - $connectTime, 3), @@ -637,6 +617,8 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError"); } + } else { + $this->logger->info('Bedrock\Client - Reusing socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); } socket_clear_error($this->socket); @@ -660,9 +642,6 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new BedrockError("Sent more content than expected to host $host:$port"); } - // Update socket usage metrics after successful send - $this->socketLastUsedTime = microtime(true); - $this->socketRequestCount++; } /** @@ -917,9 +896,6 @@ private function markHostAsFailed(string $host) if ($this->socket) { @socket_close($this->socket); $this->socket = null; - $this->socketOpenTime = null; - $this->socketLastUsedTime = null; - $this->socketRequestCount = 0; } } From 075c7d8b53bbb0e97b352bd8ee82158134201ee2 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 20:02:10 -0400 Subject: [PATCH 09/21] style --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index a286dd9..befe102 100644 --- a/src/Client.php +++ b/src/Client.php @@ -568,7 +568,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to create socket: $socketError"); } - // Set socket to non-blocking mode IMMEDIATELY after creation (PHP 8 compatibility) + // PHP 8 Socket objects need explicit non-blocking mode to prevent EINPROGRESS timeouts socket_set_nonblock($this->socket); From 778d779961a9d8f9961b187773055b7760452bfc Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Thu, 9 Oct 2025 20:03:11 -0400 Subject: [PATCH 10/21] remove empty lines --- src/Client.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Client.php b/src/Client.php index befe102..f049231 100644 --- a/src/Client.php +++ b/src/Client.php @@ -150,7 +150,6 @@ class Client implements LoggerAwareInterface */ private $logParam; - /** * Creates a reusable Bedrock instance. * All params are optional and values set in `configure` would be used if are not passed here. @@ -547,7 +546,6 @@ public function call($method, $headers = [], $body = '') return $response; } - /** * Sends the request on a new socket, if a previous one existed, it closes the connection first. * @@ -570,8 +568,6 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) // PHP 8 Socket objects need explicit non-blocking mode to prevent EINPROGRESS timeouts socket_set_nonblock($this->socket); - - socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); $connectStart = microtime(true); @@ -641,7 +637,6 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $this->logger->info('Bedrock\Client - sent more data than needed', ['bytesSent' => $bytesSent, 'expected' => strlen($rawRequest)]); throw new BedrockError("Sent more content than expected to host $host:$port"); } - } /** From 76e187e664434e8d1f8ef9136f2b3cec6e740014 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 10 Oct 2025 08:55:23 -0400 Subject: [PATCH 11/21] style again --- src/Client.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Client.php b/src/Client.php index f049231..5634aad 100644 --- a/src/Client.php +++ b/src/Client.php @@ -578,15 +578,15 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $this->logger->info('Bedrock\Client - socket_connect returned error 115, waiting for connection to complete.', [ 'host' => $host, 'connect_attempt_time_ms' => round($connectTime, 3), - 'pid' => getmypid() + 'pid' => getmypid(), ]); - + // Wait for the socket to be ready for writing after EINPROGRESS $write = [$this->socket]; $read = []; $except = []; $selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); - + if ($selectResult === false) { $socketError = socket_strerror(socket_last_error($this->socket)); throw new ConnectionFailure("socket_select failed after EINPROGRESS for $host:$port. Error: $socketError"); @@ -597,17 +597,17 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $socketError = socket_strerror($socketErrorCode); throw new ConnectionFailure("Socket had error after EINPROGRESS for $host:$port. Error: $socketErrorCode $socketError"); } - + $selectTime = (microtime(true) - $connectStart) * 1000; // Total time from connect to ready - + // Set socket back to blocking mode for normal operations socket_set_block($this->socket); - + $this->logger->info('Bedrock\Client - Socket ready for writing after EINPROGRESS.', [ 'host' => $host, 'total_connection_time_ms' => round($selectTime, 3), 'select_wait_time_ms' => round($selectTime - $connectTime, 3), - 'pid' => getmypid() + 'pid' => getmypid(), ]); } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); From dca927027a9ad1b29a0544eadac8c7711c7bbe4f Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 10 Oct 2025 08:56:09 -0400 Subject: [PATCH 12/21] bump version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a2f8c63..0cc8bcc 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "expensify/bedrock-php", "description": "Bedrock PHP Library", "type": "library", - "version": "2.2.2", + "version": "2.2.3", "authors": [ { "name": "Expensify", From 3e3c06cae74c55923f3e5b3c016ae549dea18b2b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Fri, 10 Oct 2025 08:57:23 -0400 Subject: [PATCH 13/21] 2.2.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0cc8bcc..18b9a1e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "expensify/bedrock-php", "description": "Bedrock PHP Library", "type": "library", - "version": "2.2.3", + "version": "2.2.5", "authors": [ { "name": "Expensify", From e05cc7ae86b3dc7a9ef4ec9ed7ae181af7bb7bda Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 07:58:32 -0400 Subject: [PATCH 14/21] camelcase, remove pid logging --- src/Client.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Client.php b/src/Client.php index 5634aad..18049ff 100644 --- a/src/Client.php +++ b/src/Client.php @@ -555,7 +555,6 @@ public function call($method, $headers = [], $body = '') private function sendRawRequest(string $host, int $port, string $rawRequest) { // Try to connect to the requested host - $pid = getmypid(); if (!$this->socket) { $this->logger->info('Bedrock\Client - Opening new socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); $this->socket = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); @@ -577,8 +576,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) if ($socketErrorCode === 115) { $this->logger->info('Bedrock\Client - socket_connect returned error 115, waiting for connection to complete.', [ 'host' => $host, - 'connect_attempt_time_ms' => round($connectTime, 3), - 'pid' => getmypid(), + 'connectAttemptTimeMs' => round($connectTime, 3), ]); // Wait for the socket to be ready for writing after EINPROGRESS @@ -605,9 +603,8 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $this->logger->info('Bedrock\Client - Socket ready for writing after EINPROGRESS.', [ 'host' => $host, - 'total_connection_time_ms' => round($selectTime, 3), - 'select_wait_time_ms' => round($selectTime - $connectTime, 3), - 'pid' => getmypid(), + 'totalConnectionTimeMs' => round($selectTime, 3), + 'selectWaitTimeMs' => round($selectTime - $connectTime, 3), ]); } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); From 983dfe31cab49ce6fbb7ddd9b4c25fea90f0202c Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 08:01:23 -0400 Subject: [PATCH 15/21] fix select timing --- src/Client.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Client.php b/src/Client.php index 18049ff..3b9c37c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -583,7 +583,10 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $write = [$this->socket]; $read = []; $except = []; + $selectStart = microtime(true); $selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); + // Time for socket_select call + $selectTime = (microtime(true) - $selectStart) * 1000; if ($selectResult === false) { $socketError = socket_strerror(socket_last_error($this->socket)); @@ -596,15 +599,16 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Socket had error after EINPROGRESS for $host:$port. Error: $socketErrorCode $socketError"); } - $selectTime = (microtime(true) - $connectStart) * 1000; // Total time from connect to ready + // Total time from connect to ready + $totalTime = (microtime(true) - $connectStart) * 1000; // Set socket back to blocking mode for normal operations socket_set_block($this->socket); $this->logger->info('Bedrock\Client - Socket ready for writing after EINPROGRESS.', [ 'host' => $host, - 'totalConnectionTimeMs' => round($selectTime, 3), - 'selectWaitTimeMs' => round($selectTime - $connectTime, 3), + 'totalConnectionTimeMs' => round($totalTime, 3), + 'selectWaitTimeMs' => round($selectTime, 3), ]); } elseif ($socketErrorCode) { $socketError = socket_strerror($socketErrorCode); From 6e18e6429be8d4c9c876ce50629f95e342d2934b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 08:03:24 -0400 Subject: [PATCH 16/21] clarify comment --- src/Client.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 3b9c37c..c6ecaa1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -565,7 +565,9 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to create socket: $socketError"); } - // PHP 8 Socket objects need explicit non-blocking mode to prevent EINPROGRESS timeouts + // Use non-blocking mode for connection to avoid timeouts. On non-blocking sockets, socket_connect() + // may return immediately for reasons unclear with EINPROGRESS (error 115), allowing us to use socket_select() to wait + // with a proper timeout. This prevents connection attempts from hanging indefinitely. socket_set_nonblock($this->socket); socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]); socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); From 50fce97c9bb062ea75eb9c20ebd33c0f72c5dcf8 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 08:05:39 -0400 Subject: [PATCH 17/21] style --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index c6ecaa1..edfc2b4 100644 --- a/src/Client.php +++ b/src/Client.php @@ -602,7 +602,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) } // Total time from connect to ready - $totalTime = (microtime(true) - $connectStart) * 1000; + $totalTime = (microtime(true) - $connectStart) * 1000; // Set socket back to blocking mode for normal operations socket_set_block($this->socket); From 237a35850926964fe4fa74149da07f75343be552 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 08:07:12 -0400 Subject: [PATCH 18/21] restore deleted lines --- src/Client.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index edfc2b4..4ce6477 100644 --- a/src/Client.php +++ b/src/Client.php @@ -555,6 +555,7 @@ public function call($method, $headers = [], $body = '') private function sendRawRequest(string $host, int $port, string $rawRequest) { // Try to connect to the requested host + $pid = getmypid(); if (!$this->socket) { $this->logger->info('Bedrock\Client - Opening new socket', ['host' => $host, 'cluster' => $this->clusterName, 'pid' => $pid]); $this->socket = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); @@ -565,6 +566,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) throw new ConnectionFailure("Could not connect to create socket: $socketError"); } + // Configure this socket and try to connect to it // Use non-blocking mode for connection to avoid timeouts. On non-blocking sockets, socket_connect() // may return immediately for reasons unclear with EINPROGRESS (error 115), allowing us to use socket_select() to wait // with a proper timeout. This prevents connection attempts from hanging indefinitely. @@ -573,7 +575,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]); $connectStart = microtime(true); @socket_connect($this->socket, $host, $port); - $connectTime = (microtime(true) - $connectStart) * 1000; // Convert to milliseconds + $connectTime = (microtime(true) - $connectStart) * 1000; $socketErrorCode = socket_last_error($this->socket); if ($socketErrorCode === 115) { $this->logger->info('Bedrock\Client - socket_connect returned error 115, waiting for connection to complete.', [ From 513a0847614d2d59e726b58eaf12a4b659b2f7bb Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 11:29:23 -0400 Subject: [PATCH 19/21] use 2.2.6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 18b9a1e..2cbc0f1 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "expensify/bedrock-php", "description": "Bedrock PHP Library", "type": "library", - "version": "2.2.5", + "version": "2.2.6", "authors": [ { "name": "Expensify", From 7b2161c81713b31b721460d5a080f9531321bf2b Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 12:56:41 -0400 Subject: [PATCH 20/21] add empty line before comment --- src/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Client.php b/src/Client.php index 475512a..34fb7f0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -589,6 +589,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $except = []; $selectStart = microtime(true); $selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); + // Time for socket_select call $selectTime = (microtime(true) - $selectStart) * 1000; From 890dd6be7ea08ce397d5cc948b5fff25d1f2c482 Mon Sep 17 00:00:00 2001 From: Justin Persaud Date: Tue, 14 Oct 2025 13:00:47 -0400 Subject: [PATCH 21/21] style --- src/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index 34fb7f0..f01679a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -589,7 +589,7 @@ private function sendRawRequest(string $host, int $port, string $rawRequest) $except = []; $selectStart = microtime(true); $selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds); - + // Time for socket_select call $selectTime = (microtime(true) - $selectStart) * 1000;