From 3038286a4bf7832f1c42c8cc9774ee6ff19876fc Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 15 Dec 2025 11:48:34 -0500 Subject: Fix Socket.tcp cleanup after Thread#kill (#15131) Socket.tcp launches ruby threads to resolve hostnames, and those threads communicate through a queue implemented with `IO.pipe`. When the thread that called `Socket.tcp` is killed, the resolver threads still try to communicate through the pipe even though it may be closed. The method `Socket.tcp_with_fast_fallback` tries to deal with this by killing the threads in an ensure block, and then closing the pipe. However, calling `Thread#kill` is not a blocking operation, it only sets a flag on the thread telling it to raise during the next interrupt. The thread needs to be joined to ensure it is terminated. The following script demonstrates the issue: ```ruby require "socket" ts = [] 5.times do ts << Thread.new do loop do 1_000.times do |i| puts "#{i}" t = Thread.new do Socket.tcp("ruby-lang.org", 80) end sleep 0.001 t.kill end end end end ts.each(&:join) ``` output: ``` /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#write': closed stream (IOError) from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#putc' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'block in Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Thread::Mutex#synchronize' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:980:in 'Socket.resolve_hostname' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:719:in 'block (2 levels) in Socket.tcp_with_fast_fallback' /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#write': closed stream (IOError) from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'IO#putc' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1019:in 'block in Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Thread::Mutex#synchronize' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:1017:in 'Socket::HostnameResolutionResult#add' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:978:in 'Socket.resolve_hostname' from /Users/luke/workspace/ruby-dev/ruby-build-debug/.ext/common/socket.rb:719:in 'block (2 levels) in Socket.tcp_with_fast_fallback' ``` --- ext/socket/lib/socket.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ext') diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 9862c92c0b..dae1c16760 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -917,7 +917,8 @@ class Socket < BasicSocket end ensure hostname_resolution_threads.each do |thread| - thread.exit + thread.kill + thread.join end hostname_resolution_result&.close -- cgit v1.2.3