mirror of
https://github.com/ruby/ruby.git
synced 2026-11-08 01:20:26 +08:00
Build the fast fallback of TCPSocket.new on mswin
Every piece the Happy Eyeballs Version 2 path needed from pthread, pipe(2) and select(2) now has a Windows counterpart, so a host that answers with both families no longer waits for the IPv6 attempt to time out there. MinGW stays out, since its pthread emulation was never tested against this path, and its tests are omitted instead of passing by connecting sequentially. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d81710bca7
commit
30fd927272
Notes:
git
2026-11-06 01:45:09 +00:00
12
NEWS.md
12
NEWS.md
@ -466,17 +466,23 @@ Ruby 4.0 bundled RubyGems and Bundler version 4. see the following links for det
|
||||
|
||||
* Socket
|
||||
|
||||
* `TCPSocket.new` now connects through Happy Eyeballs Version 2 on
|
||||
mswin as well, so a host that answers with both an IPv6 and an IPv4
|
||||
address no longer waits for the IPv6 attempt to time out. When it
|
||||
resolves the IPv6 and IPv4 addresses in parallel, the hostname
|
||||
resolution, which used to ignore `resolv_timeout` and `open_timeout`
|
||||
there, now raises `IO::TimeoutError` once the timeout expires.
|
||||
|
||||
* On Windows, a connection that is refused or unreachable now raises the
|
||||
matching `Errno` class as soon as Winsock reports it, instead of
|
||||
`Errno::ETIMEDOUT` once the whole `connect_timeout` has passed. Code
|
||||
rescuing `Errno::ETIMEDOUT` there has to rescue the real error instead.
|
||||
[[Bug #18661]]
|
||||
|
||||
* On Windows, `BasicSocket#getsockopt(:SOCKET, :ERROR)` now reports an
|
||||
errno as it does on the other platforms, instead of the raw WinSock
|
||||
error code. Code comparing it with a `WSAE*` value has to compare it
|
||||
with the matching `Errno::*::Errno` instead.
|
||||
|
||||
[[Bug #18661]]
|
||||
with the matching `Errno::*::Errno` instead. [[Bug #18661]]
|
||||
|
||||
## C API updates
|
||||
|
||||
|
||||
@ -420,7 +420,9 @@ char *raddrinfo_port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr)
|
||||
int raddrinfo_thread_create(void *(*start_routine) (void *), void *arg);
|
||||
|
||||
#ifndef FAST_FALLBACK_INIT_INETSOCK_IMPL
|
||||
# if !defined(HAVE_PTHREAD_CREATE) || !defined(HAVE_PTHREAD_DETACH) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
/* _WIN32 spawns the resolution threads with _beginthreadex(3) instead. */
|
||||
# if (!defined(_WIN32) && (!defined(HAVE_PTHREAD_CREATE) || !defined(HAVE_PTHREAD_DETACH))) || \
|
||||
defined(__MINGW32__) || defined(__MINGW64__)
|
||||
# define FAST_FALLBACK_INIT_INETSOCK_IMPL 0
|
||||
# else
|
||||
# include "ruby/thread_native.h"
|
||||
|
||||
@ -1738,7 +1738,8 @@ socket_s_ip_address_list(VALUE self)
|
||||
* is enabled or disabled.
|
||||
*
|
||||
* If true, it is enabled for TCPSocket.new and Socket.tcp.
|
||||
* (Note: Happy Eyeballs Version 2 is not provided when using TCPSocket.new on Windows.)
|
||||
* (Note: Happy Eyeballs Version 2 is not provided when using TCPSocket.new on MinGW builds of Windows.
|
||||
* On mswin builds of Windows, it is provided starting from Ruby 4.1.)
|
||||
*
|
||||
* If false, Happy Eyeballs Version 2 is disabled.
|
||||
*
|
||||
@ -1757,7 +1758,8 @@ VALUE socket_s_tcp_fast_fallback(VALUE self) {
|
||||
* globally, which is provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp.
|
||||
*
|
||||
* When set to true, the feature is enabled for both `TCPSocket.new` and `Socket.tcp`.
|
||||
* (Note: This feature is not available when using TCPSocket.new on Windows.)
|
||||
* (Note: This feature is not available when using TCPSocket.new on MinGW builds of Windows.
|
||||
* On mswin builds of Windows, it is available starting from Ruby 4.1.)
|
||||
*
|
||||
* When set to false, the behavior reverts to that of Ruby 3.3 or earlier.
|
||||
*
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
*
|
||||
* Starting from Ruby 3.4, this method operates according to the
|
||||
* Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
* algorithm by default, except on Windows.
|
||||
* algorithm by default, except on MinGW builds of Windows.
|
||||
* On mswin builds of Windows, it does so starting from Ruby 4.1.
|
||||
*
|
||||
* For details on Happy Eyeballs Version 2,
|
||||
* see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket.tcp_fast_fallback=].
|
||||
@ -30,7 +31,7 @@
|
||||
* Or, setting Socket.tcp_fast_fallback=false will disable
|
||||
* Happy Eyeballs Version 2 not only for this method but for all Socket globally.
|
||||
*
|
||||
* When using TCPSocket.new on Windows, Happy Eyeballs Version 2 is not provided,
|
||||
* When using TCPSocket.new on MinGW builds of Windows, Happy Eyeballs Version 2 is not provided,
|
||||
* and it behaves the same as in Ruby 3.3 and earlier.
|
||||
*
|
||||
* [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts.
|
||||
|
||||
@ -74,8 +74,26 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_tcp_initialize_resolv_timeout
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
server.close
|
||||
|
||||
assert_raise(IO::TimeoutError) do
|
||||
TCPSocket.new(
|
||||
"localhost",
|
||||
port,
|
||||
resolv_timeout: 0.01,
|
||||
fast_fallback: true,
|
||||
test_mode_settings: { delay: { ipv6: 1000, ipv4: 1000 } }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def test_tcp_initialize_open_timeout
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
@ -180,7 +198,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_earlier
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
begin
|
||||
# Verify that "localhost" can be resolved to an IPv6 address
|
||||
@ -209,7 +227,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v4_hostname_resolved_earlier
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.addr[1]
|
||||
@ -253,7 +271,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_in_resolution_delay
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
begin
|
||||
# Verify that "localhost" can be resolved to an IPv6 address
|
||||
@ -287,7 +305,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_earlier_and_v6_server_is_not_listening
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
ipv4_address = "127.0.0.1"
|
||||
server = Socket.new(Socket::AF_INET, :STREAM)
|
||||
@ -315,7 +333,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_later_and_v6_server_is_not_listening
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = Socket.new(Socket::AF_INET, :STREAM)
|
||||
server.bind(Socket.pack_sockaddr_in(0, "127.0.0.1"))
|
||||
@ -342,7 +360,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolution_failed_and_v4_hostname_resolution_is_success
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.addr[1]
|
||||
@ -386,7 +404,12 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_resolv_timeout_with_connection_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
if RUBY_PLATFORM =~ /mswin|cygwin/
|
||||
omit "Windows reports a refused loopback connection two seconds after " \
|
||||
"connect(2), so the IPv6 attempt is still in flight when " \
|
||||
"resolv_timeout expires"
|
||||
end
|
||||
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
@ -409,7 +432,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_with_hostname_resolution_failure_after_connection_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
@ -431,7 +454,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_with_connection_failure_after_hostname_resolution_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
omit_unless_fast_fallback
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
@ -463,8 +486,6 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v6_connected_socket_with_v6_address
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
||||
@ -485,8 +506,6 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_v4_connected_socket_with_v4_address
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
accepted = nil
|
||||
server_thread = Thread.new { accepted = server.accept }
|
||||
@ -502,8 +521,6 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_initialize_fast_fallback_is_false
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
_, port, = server.addr
|
||||
accepted = nil
|
||||
@ -520,6 +537,10 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
|
||||
private
|
||||
|
||||
def omit_unless_fast_fallback
|
||||
omit "TCPSocket.new does not provide Happy Eyeballs Version 2 on MinGW" if RUBY_PLATFORM =~ /mingw/
|
||||
end
|
||||
|
||||
# On success, wait for the accept thread to finish. If the test failed
|
||||
# before the client connected, `server.accept` never returns, so kill
|
||||
# the thread instead of waiting for it.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user