diff options
| author | Luke Gruber <luke.gruber@shopify.com> | 2025-12-15 11:48:34 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-15 11:48:34 -0500 |
| commit | 3038286a4bf7832f1c42c8cc9774ee6ff19876fc (patch) | |
| tree | 74a6b8f188baf13cdf9d83fbe5a87c696ea39d7a /ext | |
| parent | 6b63b0cbeb33c8606f147b0293e9b8ed8c34a0e5 (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.rb | 3 |
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 |
