summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorLuke Gruber <luke.gruber@shopify.com>2025-12-15 11:48:34 -0500
committerGitHub <noreply@github.com>2025-12-15 11:48:34 -0500
commit3038286a4bf7832f1c42c8cc9774ee6ff19876fc (patch)
tree74a6b8f188baf13cdf9d83fbe5a87c696ea39d7a /ext
parent6b63b0cbeb33c8606f147b0293e9b8ed8c34a0e5 (diff)
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' ```
Diffstat (limited to 'ext')
-rw-r--r--ext/socket/lib/socket.rb3
1 files changed, 2 insertions, 1 deletions
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