summaryrefslogtreecommitdiff
path: root/test/-ext-
diff options
context:
space:
mode:
authorJohn Hawthorn <john@hawthorn.email>2023-11-29 15:54:32 -0800
committerJohn Hawthorn <john@hawthorn.email>2023-12-02 10:06:07 -0800
commitad54fbf281ca1935e79f4df1460b0106ba76761e (patch)
treeae560415a2804342a0e7ef13f7cf6e245752960c /test/-ext-
parent715cf9b6902b0cc317fbc5cea0df153e14ff7277 (diff)
Add missing GVL hooks for M:N threads and ractors
[Bug #20019] This fixes GVL instrumentation in three locations it was missing: - Suspending when blocking on a Ractor - Suspending when doing a coroutine transfer from an M:N thread - Resuming after an M:N thread starts Co-authored-by: Matthew Draper <matthew@trebex.net>
Diffstat (limited to 'test/-ext-')
-rw-r--r--test/-ext-/thread/helper.rb51
-rw-r--r--test/-ext-/thread/test_instrumentation_api.rb84
2 files changed, 89 insertions, 46 deletions
diff --git a/test/-ext-/thread/helper.rb b/test/-ext-/thread/helper.rb
new file mode 100644
index 0000000000..3ea2057d15
--- /dev/null
+++ b/test/-ext-/thread/helper.rb
@@ -0,0 +1,51 @@
+module ThreadInstrumentation
+ module TestHelper
+ private
+
+ def record
+ Bug::ThreadInstrumentation.register_callback(!ENV["GVL_DEBUG"])
+ yield
+ ensure
+ timeline = Bug::ThreadInstrumentation.unregister_callback
+ if $!
+ raise
+ else
+ return timeline
+ end
+ end
+
+ def timeline_for(thread, timeline)
+ timeline.select { |t, _| t == thread }.map(&:last)
+ end
+
+ def assert_consistent_timeline(events)
+ refute_predicate events, :empty?
+
+ previous_event = nil
+ events.each do |event|
+ refute_equal :exited, previous_event, "`exited` must be the final event: #{events.inspect}"
+ case event
+ when :started
+ assert_nil previous_event, "`started` must be the first event: #{events.inspect}"
+ when :ready
+ unless previous_event.nil?
+ assert %i(started suspended).include?(previous_event), "`ready` must be preceded by `started` or `suspended`: #{events.inspect}"
+ end
+ when :resumed
+ unless previous_event.nil?
+ assert_equal :ready, previous_event, "`resumed` must be preceded by `ready`: #{events.inspect}"
+ end
+ when :suspended
+ unless previous_event.nil?
+ assert_equal :resumed, previous_event, "`suspended` must be preceded by `resumed`: #{events.inspect}"
+ end
+ when :exited
+ unless previous_event.nil?
+ assert %i(resumed suspended).include?(previous_event), "`exited` must be preceded by `resumed` or `suspended`: #{events.inspect}"
+ end
+ end
+ previous_event = event
+ end
+ end
+ end
+end
diff --git a/test/-ext-/thread/test_instrumentation_api.rb b/test/-ext-/thread/test_instrumentation_api.rb
index ef3b8358b3..6ebdf3eac2 100644
--- a/test/-ext-/thread/test_instrumentation_api.rb
+++ b/test/-ext-/thread/test_instrumentation_api.rb
@@ -1,7 +1,10 @@
# frozen_string_literal: false
require 'envutil'
+require_relative "helper"
class TestThreadInstrumentation < Test::Unit::TestCase
+ include ThreadInstrumentation::TestHelper
+
def setup
pend("No windows support") if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
@@ -131,6 +134,41 @@ class TestThreadInstrumentation < Test::Unit::TestCase
assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
end
+ def test_blocking_on_ractor
+ assert_ractor(<<-"RUBY", require_relative: "helper", require: "-test-/thread/instrumentation")
+ include ThreadInstrumentation::TestHelper
+
+ full_timeline = record do
+ Ractor.new{
+ Thread.current
+ }.take
+ end
+
+ timeline = timeline_for(Thread.current, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(suspended ready resumed), timeline
+ RUBY
+ end
+
+ def test_sleeping_inside_ractor
+ assert_ractor(<<-"RUBY", require_relative: "helper", require: "-test-/thread/instrumentation")
+ include ThreadInstrumentation::TestHelper
+
+ thread = nil
+
+ full_timeline = record do
+ thread = Ractor.new{
+ sleep 0.1
+ Thread.current
+ }.take
+ end
+
+ timeline = timeline_for(thread, full_timeline)
+ assert_consistent_timeline(timeline)
+ assert_equal %i(started ready resumed suspended ready resumed suspended exited), timeline
+ RUBY
+ end
+
def test_thread_blocked_forever_on_mutex
mutex = Mutex.new
mutex.lock
@@ -205,52 +243,6 @@ class TestThreadInstrumentation < Test::Unit::TestCase
private
- def record
- Bug::ThreadInstrumentation.register_callback(!ENV["GVL_DEBUG"])
- yield
- ensure
- timeline = Bug::ThreadInstrumentation.unregister_callback
- if $!
- raise
- else
- return timeline
- end
- end
-
- def assert_consistent_timeline(events)
- refute_predicate events, :empty?
-
- previous_event = nil
- events.each do |event|
- refute_equal :exited, previous_event, "`exited` must be the final event: #{events.inspect}"
- case event
- when :started
- assert_nil previous_event, "`started` must be the first event: #{events.inspect}"
- when :ready
- unless previous_event.nil?
- assert %i(started suspended).include?(previous_event), "`ready` must be preceded by `started` or `suspended`: #{events.inspect}"
- end
- when :resumed
- unless previous_event.nil?
- assert_equal :ready, previous_event, "`resumed` must be preceded by `ready`: #{events.inspect}"
- end
- when :suspended
- unless previous_event.nil?
- assert_equal :resumed, previous_event, "`suspended` must be preceded by `resumed`: #{events.inspect}"
- end
- when :exited
- unless previous_event.nil?
- assert %i(resumed suspended).include?(previous_event), "`exited` must be preceded by `resumed` or `suspended`: #{events.inspect}"
- end
- end
- previous_event = event
- end
- end
-
- def timeline_for(thread, timeline)
- timeline.select { |t, _| t == thread }.map(&:last)
- end
-
def fib(n = 30)
return n if n <= 1
fib(n-1) + fib(n-2)