Skip to content

Commit 02142e5

Browse files
Merge pull request #242 from Expensify/jpersaud_socket_not_Ready
Wait for socket to be ready before connecting on 115
2 parents e8b07b4 + 890dd6b commit 02142e5

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "expensify/bedrock-php",
33
"description": "Bedrock PHP Library",
44
"type": "library",
5-
"version": "2.2.5",
5+
"version": "2.2.6",
66
"authors": [
77
{
88
"name": "Expensify",

src/Client.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,12 +567,54 @@ private function sendRawRequest(string $host, int $port, string $rawRequest)
567567
}
568568

569569
// Configure this socket and try to connect to it
570+
// Use non-blocking mode for connection to avoid timeouts. On non-blocking sockets, socket_connect()
571+
// may return immediately for reasons unclear with EINPROGRESS (error 115), allowing us to use socket_select() to wait
572+
// with a proper timeout. This prevents connection attempts from hanging indefinitely.
573+
socket_set_nonblock($this->socket);
570574
socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => $this->connectionTimeout, 'usec' => $this->connectionTimeoutMicroseconds]);
571575
socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $this->readTimeout, 'usec' => $this->readTimeoutMicroseconds]);
576+
$connectStart = microtime(true);
572577
@socket_connect($this->socket, $host, $port);
578+
$connectTime = (microtime(true) - $connectStart) * 1000;
573579
$socketErrorCode = socket_last_error($this->socket);
574580
if ($socketErrorCode === 115) {
575-
$this->logger->info('Bedrock\Client - socket_connect returned error 115, continuing.');
581+
$this->logger->info('Bedrock\Client - socket_connect returned error 115, waiting for connection to complete.', [
582+
'host' => $host,
583+
'connectAttemptTimeMs' => round($connectTime, 3),
584+
]);
585+
586+
// Wait for the socket to be ready for writing after EINPROGRESS
587+
$write = [$this->socket];
588+
$read = [];
589+
$except = [];
590+
$selectStart = microtime(true);
591+
$selectResult = socket_select($read, $write, $except, $this->connectionTimeout, $this->connectionTimeoutMicroseconds);
592+
593+
// Time for socket_select call
594+
$selectTime = (microtime(true) - $selectStart) * 1000;
595+
596+
if ($selectResult === false) {
597+
$socketError = socket_strerror(socket_last_error($this->socket));
598+
throw new ConnectionFailure("socket_select failed after EINPROGRESS for $host:$port. Error: $socketError");
599+
} elseif ($selectResult === 0) {
600+
throw new ConnectionFailure("Socket not ready for writing within timeout after EINPROGRESS for $host:$port");
601+
} elseif (empty($write)) {
602+
$socketErrorCode = socket_last_error($this->socket);
603+
$socketError = socket_strerror($socketErrorCode);
604+
throw new ConnectionFailure("Socket had error after EINPROGRESS for $host:$port. Error: $socketErrorCode $socketError");
605+
}
606+
607+
// Total time from connect to ready
608+
$totalTime = (microtime(true) - $connectStart) * 1000;
609+
610+
// Set socket back to blocking mode for normal operations
611+
socket_set_block($this->socket);
612+
613+
$this->logger->info('Bedrock\Client - Socket ready for writing after EINPROGRESS.', [
614+
'host' => $host,
615+
'totalConnectionTimeMs' => round($totalTime, 3),
616+
'selectWaitTimeMs' => round($selectTime, 3),
617+
]);
576618
} elseif ($socketErrorCode) {
577619
$socketError = socket_strerror($socketErrorCode);
578620
throw new ConnectionFailure("Could not connect to Bedrock host $host:$port. Error: $socketErrorCode $socketError");

0 commit comments

Comments
 (0)