@@ -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