Project

General

Profile

Actions

Bug #21790

open

`Socket.getaddrinfo` hangs after `fork()` on macOS 26.1 (Tahoe) for IPv4-only hosts

Bug #21790: `Socket.getaddrinfo` hangs after `fork()` on macOS 26.1 (Tahoe) for IPv4-only hosts

Added by adamoffat (Adam Moffat) about 3 hours ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:124288]

Description

Ruby's Socket.getaddrinfo hangs indefinitely in forked child processes on macOS 26.1 (Tahoe) when resolving IPv4-only hostnames. This is a regression that does not occur on macOS 15.x (Sonoma) or earlier.

Ruby version:
ruby 3.3.8 (2025-04-09 revision b200bad6cd) [arm64-darwin24]
Also confirmed this affects Ruby 3.2.6 and 3.4.1.

Reproducible script:

require "socket"
require "timeout"

puts "Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
Socket.getaddrinfo("api.segment.io", 443, nil, :STREAM)
puts "Parent: DNS completed"

pid = fork do
  puts "Child: Attempting DNS resolution..."
  begin
    Timeout.timeout(90) do
      Socket.getaddrinfo("api.segment.io", 443, nil, :STREAM)
    end
    puts "Child: SUCCESS"
    exit 0
  rescue Timeout::Error
      puts "Child: FAILED - hung for 90 seconds"
      exit 1
  end
end

Process.wait(pid)

Note: Remove the Timeout.timeout(90) wrapper to observe the hang indefinitely. The timeout is included only to allow the script to exit for testing purposes.

Result of reproduce process:

Ruby 3.3.8 on arm64-darwin24
Parent: DNS completed
Child: Attempting DNS resolution...
Child: FAILED - hung for 90 seconds

The child process hangs with one thread consuming 100% CPU.

Expected result: The child process should complete DNS resolution successfully, as it does on macOS 15.x and earlier.

Analysis:
Stack trace shows:
Main thread: Blocked in wait_getaddrinfo_pthread_cond_wait
DNS thread: Spinning in _gai_nat64_second_passnw_path_access_agent_cache_os_log_preferences_refreshSIGSEGV

The crash occurs in macOS's NAT64 synthesis code path. Ruby's signal handler catches the SIGSEGV but cannot recover, causing the DNS thread to spin.

Key observations:

  • Only affects IPv4-only hosts. Hosts with IPv6 (like google.com) work correctly.
  • Using AF_INET instead of AF_UNSPEC works. Socket.getaddrinfo("api.segment.io", 443, Socket::AF_INET, :STREAM) succeeds.
  • Python is not affected. Python calls getaddrinfo() synchronously without a background thread.
  • Parent must do DNS before fork. If the parent has not called getaddrinfo(), the child works correctly.

Workaround:

  • Use resolv-replace to bypass the native DNS resolver: require "resolv-replace"

Impact:
This breaks all Ruby applications using pre-forking worker models (Resque, Unicorn, Puma, Sidekiq, Passenger) on macOS Tahoe.

Apple Bug Report:
Filed with Apple as Feedback Assistant #FB21364061


Files

stack_trace.txt (66.6 KB) stack_trace.txt Stack Trace for Bug adamoffat (Adam Moffat), 12/17/2025 05:56 PM
ruby_dns_fork_bug.rb (1.02 KB) ruby_dns_fork_bug.rb Reproduction Script adamoffat (Adam Moffat), 12/17/2025 06:02 PM

No data to display

Actions

Also available in: PDF Atom