Skip to content

Moving Threads to Threadgroup::Default should not happen for enclosed ThreadGroups #24

@gamecreature

Description

@gamecreature

Moving the Thread to ThreadGroup::Default causes issues with enclosed ThreadGroups. (this change happend in commit c4f1385)

It goes wrong in for example the airbrake-ruby gem. (airbrake/airbrake-ruby#713 (comment))

  • The airbrake-ruby notifier creates a ThreadGroup with workers.
  • It encloses this group.
  • A worker starts a Net::Http request. This request creates the timeout.
  • The timeout thread is created in the enclosed worker ThreadGroup
  • An attempt is made to move the Thread to ThreadGroup::Default
  • Which results in the following exception:
#<Thread:0x0000000107da1e40 /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-ruby-6.2.0/lib/airbrake-ruby/thread_pool.rb:131 run> terminated with exception (report_on_exception is true):
/Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:123:in `add': can't move from the enclosed thread group (ThreadError)
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:123:in `create_timeout_thread'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:134:in `block in ensure_timeout_thread_created'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:132:in `synchronize'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:132:in `ensure_timeout_thread_created'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/timeout.rb:181:in `timeout'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/net/http.rb:1269:in `connect'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/net/http.rb:1248:in `do_start'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/net/http.rb:1237:in `start'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/3.2.0/net/http.rb:1817:in `request'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-13.0.3/lib/airbrake/rails/net_http.rb:11:in `block in request'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-13.0.3/lib/airbrake/rack.rb:21:in `capture_timing'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-13.0.3/lib/airbrake/rails/net_http.rb:10:in `request'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-ruby-6.2.0/lib/airbrake-ruby/sync_sender.rb:49:in `send'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-ruby-6.2.0/lib/airbrake-ruby/async_sender.rb:51:in `block in thread_pool'
	from /Users/rick/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/airbrake-ruby-6.2.0/lib/airbrake-ruby/thread_pool.rb:135:in `block in spawn_worker'	

This problem could be solved by making lib/timeout.rb:123 conditional

ThreadGroup::Default.add(watcher) unless watcher.group.enclosed?

Example to illustrate this behavior:

# This sample fails!
t1 = Thread.new {
  sleep(2)
  t2 = Thread.new { }
  puts "t2.group: #{t2.group}"
  ThreadGroup::Default.add(t2) 
  puts "Success!"
}
group = ThreadGroup.new
group.add(t1)
group.enclose
puts "t1.group: #{t1.group}"
t1.join
# This sample succeeds by adding the conditional
t1 = Thread.new {
  sleep(2)
  t2 = Thread.new { }
  puts "t2.group: #{t2.group}"
  ThreadGroup::Default.add(t2) unless t2.group.enclosed?
  puts "Success!"
}
group = ThreadGroup.new
group.add(t1)
group.enclose
puts "t1.group: #{t1.group}"
t1.join

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions