From 0100856d9ebdaee4b306646e0f939f31a80b4b04 Mon Sep 17 00:00:00 2001 From: Jordan Brough Date: Tue, 22 Sep 2026 07:29:03 -0600 Subject: [PATCH] Make the fixture-server readiness check detect refused connections TestServerManager#wait_for_port (and port_open?) probed the fixture servers with Socket.tcp(host, port, connect_timeout: 1). On Darwin with Ruby <= 4.0.6, Socket.tcp returns a socket for a refused connection because the retry connect(2) answers EISCONN and the library does not check SO_ERROR (ruby-core Bug #22223, fixed in Ruby 4.0.7). The readiness check therefore returned immediately after spawn, and the first spec to touch a server raced its boot: "Connection refused" on the streamable transport and "SSE transport closed" for every example in the SSE files, depending on which file ran first. Use TCPSocket.new(host, port, connect_timeout: 1), whose C connect path checks SO_ERROR and raises ECONNREFUSED reliably, so start_server blocks until every fixture port is actually accepting connections. --- spec/support/test_server_manager.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/support/test_server_manager.rb b/spec/support/test_server_manager.rb index e519480a..672cefa4 100644 --- a/spec/support/test_server_manager.rb +++ b/spec/support/test_server_manager.rb @@ -160,7 +160,7 @@ def external_servers_running? end def port_open?(port, host = "127.0.0.1") - Socket.tcp(host, port, connect_timeout: 1).close + TCPSocket.new(host, port, connect_timeout: 1).close true rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH false @@ -202,10 +202,14 @@ def stop_server_type(server_type) end end + # TCPSocket.new is used instead of Socket.tcp because Socket.tcp(connect_timeout:) + # on Ruby <= 4.0.6 returns a socket for a refused connection on Darwin (the retry + # connect(2) answers EISCONN, ruby-core Bug #22223), which made this readiness + # check a no-op and let the first spec race the fixture server boot. def wait_for_port(port, host = "127.0.0.1", timeout = 15) with_timeout(timeout) do loop do - Socket.tcp(host, port, connect_timeout: 1).close + TCPSocket.new(host, port, connect_timeout: 1).close break rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH sleep 0.1