This suppresses this warning:
../../../ext/socket/ipsocket.c: In function ‘rsock_raise_user_specified_timeout’:
../../../ext/socket/ipsocket.c:30:1: warning: function might be candidate for attribute ‘noreturn’ [-Wsuggest-attribute=noreturn]
30 | rsock_raise_user_specified_timeout()
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* [Feature #21347] Add `open_timeout` as an overall timeout option for `TCPSocket.new`
With this change, `TCPSocket.new` now accepts the `open_timeout` option.
This option raises an exception if the specified number of seconds has elapsed since the start of the method call,
even if the operation is still in the middle of name resolution or connection attempts.
The addition of this option follows the same intent as the previously merged change to `Socket.tcp`.
[Feature #21347](https://bugs.ruby-lang.org/issues/21347)
https://github.com/ruby/ruby/pull/13368
* Tidy up: Extract rsock_raise_user_specified_timeout()
* Added a note to the documentation of `Socket.tcp`
* Fix `rsock_init_inetsock` for `FAST_FALLBACK_INIT_INETSOCK_IMPL`
[Bug #21506] Fix timeout in Addrinfo.getaddrinfo to actually take effect
This change fixes an issue where the timeout option in `Addrinfo.getaddrinfo` was not functioning as expected.
It also addresses a related issue where specifying `fast_fallback: false` with `resolv_timeout` for `Socket.tcp` and`TCPSocket.new` would have no effect.
The timeout option was originally introduced in:
6382f5cc91
However, the value was noy used in current implementation:
3f0e0d5c8b/ext/socket/raddrinfo.c (L1282-1308)
Therefore, even if a timeout is specified and the duration elapses during name resolution, nothing happens. This is clearly not the intended behavior.
`Addrinfo.getaddrinfo` has been made interruptible as of Feature #19965.
This change uses that feature to interrupt name resolution when the specified timeout period elapses, raising a user-specified timeout error.
The timeout can be specified in milliseconds.
The same issue affects `Socket.tcp` and `TCPSocket.new` when `resolv_timeout` is set along with `fast_fallback: false`.
`resolv_timeout` was introduced in the following commits:
6382f5cc91511fe23fa2
The reason is that with `fast_fallback: false`, these methods internally call the same `rsock_getaddrinfo()` as `Addrinfo.getaddrinfo`.
This change addresses that as well.
This change addresses the following ASAN error:
```
==36597==ERROR: AddressSanitizer: heap-use-after-free on address 0x512000396ba8 at pc 0x7fcad5cbad9f bp 0x7fff19739af0 sp 0x7fff19739ae8
WRITE of size 8 at 0x512000396ba8 thread T0
[643/756] 36600=optparse/test_summary
#0 0x7fcad5cbad9e in free_fast_fallback_getaddrinfo_entry /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/raddrinfo.c:3046:22
#1 0x7fcad5c9fb48 in fast_fallback_inetsock_cleanup /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/ipsocket.c:1179:17
#2 0x7fcadf3b611a in rb_ensure /home/runner/work/ruby-dev-builder/ruby-dev-builder/eval.c:1081:5
#3 0x7fcad5c9b44b in rsock_init_inetsock /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/ipsocket.c:1289:20
#4 0x7fcad5ca22b8 in tcp_init /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/tcpsocket.c:76:12
#5 0x7fcadf83ba70 in vm_call0_cfunc_with_frame /home/runner/work/ruby-dev-builder/ruby-dev-builder/./vm_eval.c:164:15
...
```
A `struct fast_fallback_getaddrinfo_shared` is shared between the main thread and two child threads.
This struct contains an array of `fast_fallback_getaddrinfo_entry`.
`fast_fallback_getaddrinfo_entry` and `fast_fallback_getaddrinfo_shared` were freed separately, and if `fast_fallback_getaddrinfo_shared` was freed first and then an attempt was made to free a `fast_fallback_getaddrinfo_entry`, a `heap-use-after-free` could occur.
This change avoids that possibility by separating the deallocation of the addrinfo memory held by `fast_fallback_getaddrinfo_entry` from the access and lifecycle of the `fast_fallback_getaddrinfo_entry` itself.
* Do not save ResolutionError if resolution succeeds for any address family
Socket with Happy Eyeballs Version 2 performs connection attempts and name resolution in parallel.
In the existing implementation, if a connection attempt failed for one address family while name resolution was still in progress for the other, and that name resolution later failed, the method would terminate with a name resolution error.
This behavior was intended to ensure that the final error reflected the most recent failure, potentially overriding an earlier error.
However, [Bug #21088](https://bugs.ruby-lang.org/issues/21088) made me realize that terminating with a name resolution error is unnatural when name resolution succeeded for at least one address family.
This PR modifies the behavior so that if name resolution succeeds for one address family, any name resolution error from the other is not saved.
This PR includes the following changes:
* Do not display select(2) as the system call that caused the raised error, as it is for internal processing
* Fix bug: Get errno with Socket::SO_ERROR in Windows environment with a workaround for tests not passing
Rarely, there are cases where a write(2) call from a child thread
to notify the main thread of the completion of name resolution fails.
If this happens while the main thread is waiting in `rb_thread_fd_select`,
rb_thread_fd_select may not notice that the name resolution has completed and end up hanging.
This issue becomes a problem when there are no sockets currently being connected,
no addresses ready for immediate connection attempts,
and name resolution has already completed for one address family
while the main thread is waiting for the name resolution of the other address family.
(If name resolution is not completed for either address family,
the chances of write(2) failing in both child threads are likely low.)
To avoid this issue, a timeout is introduced to rb_thread_fd_select under the above conditions.
This way, even if the issue occurs,
the completion of name resolution should still be detected
in the subsequent `if (!resolution_store.is_all_finished) ...` block.
Wrap `do_fast_fallback_getaddrinfo` with `rb_thread_prevent_fork`
Referencing PR #10864,
wrap `do_fast_fallback_getaddrinfo` with `rb_thread_prevent_fork`
to avoid fork safety issues.
`do_fast_fallback_getaddrinfo` internally uses getaddrinfo(3),
leading to fork safety issues, as described in PR #10864.
This change ensures that `do_fast_fallback_getaddrinfo`
is guarded by `rb_thread_prevent_fork`,
preventing fork during its execution and avoiding related issues.
Any memory allocated with xmalloc needs to be matched with xfree rather
than plain free.
Ruby unfortunately redefines strdup to be ruby_strdup, which uses
xmalloc so needs to be xfreed. Previously these were mismatched.
This commit changes the copy to be an explicit ruby_strdup (to avoid
confusion) and the free to be xfree.
* Use `rb_thread_fd_select` instead of select(2)
For fixing https://bugs.ruby-lang.org/issues/20932 .
`TCPSocket.new`, which internally uses select(2) for HEv2, can cause SEGV if the number of file descriptors exceeds `FD_SETSIZE`.
This change avoids that issue by replacing select(2) with `rb_thread_fd_select`, which is provided as part of Ruby's internal API.
---
This includes the following changes.
* rb_thread_fd_select does not need common pipe
* Improve the conditions for clearing the Connection Attempt Delay upon connection failure
This change addresses a case that was overlooked in ruby/ruby#12087.
In the previous change, the Connection Attempt Delay was cleared at the point of a connection failure only if both of the following conditions were met:
- No other sockets were attempting a connection
- There were addresses still available to start a new connection
In this update, the second condition has been removed.
As a result, if name resolution succeeds after a connection failure and new addresses are obtained, it will be able to immediately attempt a connection to one of them.
If there are no sockets attempting a connection, no addresses available for connection, and name resolution has completed, an exception will still be raised as before.
---
Additionally, the following minor fixes have been made:
* Refactor: Remove unnecessary members
`TCPSocket.new` with HEv2 uses three threads.
The last of these threads to exit closed pipes.
However, if pipes were open at the end of the main thread, they would leak.
This change avoids this by closing pipes at the end of the main thread.
With https://github.com/ruby/ruby/pull/12156,
the memory of the `struct fast_fallback_getaddrinfo_shared`
is now allocated even if there is only one address family.
This change will always free it when `TCPSocket.new` finishes.
```
for (int i = 0; i < arg->family_size; i++) {
arg->getaddrinfo_entries[i] = allocate_fast_fallback_getaddrinfo_entry();
if (!(arg->getaddrinfo_entries[i])) rb_syserr_fail(errno, "calloc(3)");
```
If the allocation fails in the second interation, the memory allocated
in the first iteration would be leaked.
This change prevents the memory leak by allocating the memory in
advance.
(The struct name `fast_fallback_getaddrinfo_shared` might no longer be
good.)
* Do not save the last_error if there are no sockets waiting to be connected
In this implementation, the results of both name resolution and connection attempts are awaited using select(2).
When it returned, the implementation attempted to check for connections even if there were no sockets currently attempting to connect, treating the absence of connected sockets as a connection failure.
With this fix, it will no longer check for connections when there are no sockets waiting to be connected.
Additionally, the following minor fixes have been made:
* Handle failure of getsockopt(2) and removed unnecessary continue in the loop
* Tweak: Use common API to check in_progress_fds
* Safely call TCPServer.new in test
* Set empty writefds when there is no socket waiting to be connected
* Enable fast_fallback option
`rb_thread_call_without_gvl2` is used to wait for the results of name resolution and connection attempts.
When there is only one address family to resolve, the necessary resources were not being passed to the UBF.
With this change, the handling of resources has been revised and organized to work consistently, whether there are two address families to resolve or only one.
even if a system call error happens after the name resolution failure in the child thread.
pipe and write(2) are used to notify the main thread of the name resolution results from the child thread.
After name resolution is completed in the child thread, if the call to write(2) fails, the main thread retrieves the resolved addresses.
However, when name resolution failed, the corresponding error was not being saved in `last_error`.
With this change, name resolution failures will now be saved in last_error even if the write(2) call in the child thread fails.
http://ci.rvm.jp/results/trunk-repeat50@ruby-sp2-noble-docker/5420911
```
/tmp/ruby/src/trunk-repeat50/ext/socket/ipsocket.c: In function ‘reallocate_connection_attempt_fds’:
/tmp/ruby/src/trunk-repeat50/ext/socket/ipsocket.c:292:62: warning: pointer ‘fds’ may be used after ‘realloc’ [-Wuse-after-free]
292 | for (int i = current_capacity; i < new_capacity; i++) fds[i] = -1;
| ^
/tmp/ruby/src/trunk-repeat50/ext/socket/ipsocket.c:288:9: note: call to ‘realloc’ here
288 | if (realloc(fds, new_capacity * sizeof(int)) == NULL) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
http://ci.rvm.jp/results/trunk_asan@ruby-sp1/5409001
```
=================================================================
==3263562==ERROR: AddressSanitizer: stack-use-after-return on address 0x735a8f190da8 at pc 0x735a6f58dabc bp 0x735a639ffd10 sp 0x735a639ffd08
READ of size 4 at 0x735a8f190da8 thread T211
=================================================================
```
Do not wait Connection Attempt Delay without in progress fds
Reset Connection Attempt Delay when connection fails and there is no other socket connection in progress.
This is intended to resolve an issue that was temporarily worked around in Pull Request #12062.
`TCPServer::new` (used in tests such as `TestNetHTTP_v1_2_chunked#test_timeout_during_non_chunked_streamed_HTTP_session_write`) can only connect over either IPv6 or IPv4, depending on the environment.
Since HEv2 attempts to connect over IPv6 first, environments where IPv6 connections are unavailable return ECONNREFUSED immediately.
In such cases, the client should immediately retry the connection over IPv4.
However, HEv2 includes a specification for a "Connection Attempt Delay," where it waits 250ms after the previous connection attempt before starting the next one.
This delay causes Net::OpenTimeout (100ms) to be exceeded while waiting for the next connection attempt to start.
With this change, when a connection attempt fails, if there are sockets still attempting to connect and there are addresses yet to be tried, the Connection Attempt Delay will be resetted, allowing the next connection attempt to start immediately.
---
Additionally, the following minor fixes have been made:
- The `nfds` value used for select(2) is now reset with each wait.
* Introduction of Happy Eyeballs Version 2 (RFC8305) in TCPSocket.new
This is an implementation of Happy Eyeballs version 2 (RFC 8305) in `TCPSocket.new`.
See https://github.com/ruby/ruby/pull/11653
1. Background
Prior to this implementation, I implemented Happy Eyeballs Version 2 (HEv2) for `Socket.tcp` in https://github.com/ruby/ruby/pull/9374.
HEv2 is an algorithm defined in [RFC 8305](https://datatracker.ietf.org/doc/html/rfc8305), aimed at improving network connectivity.
For more details on the specific cases that HEv2 helps, please refer to https://bugs.ruby-lang.org/issues/20108.
2. Proposal & Outcome
This proposal implements the same HEv2 algorithm in `TCPSocket.new`.
Since `TCPSocket.new` is used more widely than `Socket.tcp`, this change is expected to broaden the impact of HEv2's benefits.
Like `Socket.tcp`, I have also added `fast_fallback` keyword argument to `TCPSocket.new`.
This option is set to true by default, enabling the HEv2 functionality.
However, users can explicitly set it to false to disable HEv2 and use the previous behavior of `TCPSocket.new`.
It should be noted that HEv2 is enabled only in environments where pthreads are available.
This specification follows the approach taken in https://bugs.ruby-lang.org/issues/19965 , where name resolution can be interrupted.
(In environments where pthreads are not available, the `fast_fallback` option is ignored.)
3. Performance
Below is the benchmark of 100 requests to `www.ruby-lang.org` with the fast_fallback option set to true and false, respectively.
While there is a slight performance degradation when HEv2 is enabled, the degradation is smaller compared to that seen in `Socket.tcp`.
```
~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb
Rehearsal --------------------------------------------------------
fast_fallback: true 0.017588 0.097045 0.114633 ( 1.460664)
fast_fallback: false 0.014033 0.078984 0.093017 ( 1.413951)
----------------------------------------------- total: 0.207650sec
user system total real
fast_fallback: true 0.020891 0.124054 0.144945 ( 1.473816)
fast_fallback: false 0.018392 0.110852 0.129244 ( 1.466014)
```
* Update debug prints
Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
* Remove debug prints
* misc
* Disable HEv2 in Win
* Raise resolution error with hostname resolution
* Fix to handle errors
* Remove warnings
* Errors that do not need to be handled
* misc
* Improve doc
* Fix bug on cancellation
* Avoid EAI_ADDRFAMILY for resolving IPv6
* Follow upstream
* misc
* Refactor connection_attempt_fds management
- Introduced allocate_connection_attempt_fds and reallocate_connection_attempt_fds for improved memory allocation of connection_attempt_fds
- Added remove_connection_attempt_fd to resize connection_attempt_fds dynamically.
- Simplified the in_progress_fds function to only check the size of connection_attempt_fds.
* Rename do_pthread_create to raddrinfo_pthread_create to avoid conflicting
---------
Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
When making an outgoing TCP or UDP connection, set AI_ADDRCONFIG in the
hints we send to getaddrinfo(3) (if supported). This will prompt the
resolver to _NOT_ issue A or AAAA queries if the system does not
actually have an IPv4 or IPv6 address (respectively).
This makes outgoing connections marginally more efficient on
non-dual-stack systems, since we don't have to try connecting to an
address which can't possibly work.
More importantly, however, this works around a race condition present
in some older versions of glibc on aarch64 where it could accidently
send the two outgoing DNS queries with the same DNS txnid, and get
confused when receiving the responses. This manifests as outgoing
connections sometimes taking 5 seconds (the DNS timeout before retry) to
be made.
Fixes#19144
getaddrinfo_a() gets stuck after fork().
To avoid this, we need 1 second sleep to wait for internal
worker threads of getaddrinfo_a() to be finished, but that is unacceptable.
[Bug #17220] [Feature #17134] [Feature #17187]
After 5e86b005c0, I now think ANYARGS is
dangerous and should be extinct. This commit deletes ANYARGS from
rb_ensure, which also revealed many arity / type mismatches.
* ext/socket/ipsocket.c (init_inetsock_internal): preserve errno
before other library calls and use rb_syserr_fail.
[ruby-core:68531] [Bug #10975]
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@50404 b2dd03c8-39d4-4d8f-98ff-823fe69b080e