diff options
Diffstat (limited to 'test/ruby/test_thread.rb')
| -rw-r--r-- | test/ruby/test_thread.rb | 485 |
1 files changed, 370 insertions, 115 deletions
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 52d46dc7a3..41cee124be 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1,7 +1,8 @@ # -*- coding: us-ascii -*- # frozen_string_literal: false require 'test/unit' -require 'thread' +require "rbconfig/sizeof" +require "timeout" class TestThread < Test::Unit::TestCase class Thread < ::Thread @@ -28,12 +29,29 @@ class TestThread < Test::Unit::TestCase end def test_inspect + line = __LINE__+1 th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{} - assert_match(/::C\u{30b9 30ec 30c3 30c9}:/, th.inspect) + s = th.inspect + assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:") + assert_include(s, " #{__FILE__}:#{line} ") + assert_equal(s, th.to_s) ensure th.join end + def test_inspect_with_fiber + inspect1 = inspect2 = nil + + Thread.new{ + inspect1 = Thread.current.inspect + Fiber.new{ + inspect2 = Thread.current.inspect + }.resume + }.join + + assert_equal inspect1, inspect2, '[Bug #13689]' + end + def test_main_thread_variable_in_enumerator assert_equal Thread.main, Thread.current @@ -90,7 +108,7 @@ class TestThread < Test::Unit::TestCase def test_thread_variable_frozen t = Thread.new { }.join t.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do t.thread_variable_set(:foo, "bar") end end @@ -154,6 +172,8 @@ class TestThread < Test::Unit::TestCase t1.kill t2.kill assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + t1.join + t2.join end def test_new @@ -172,8 +192,8 @@ class TestThread < Test::Unit::TestCase end ensure - t1.kill if t1 - t2.kill if t2 + t1&.kill&.join + t2&.kill&.join end def test_new_symbol_proc @@ -189,7 +209,7 @@ class TestThread < Test::Unit::TestCase assert_nil(t.join(0.05)) ensure - t.kill if t + t&.kill&.join end def test_join2 @@ -210,9 +230,47 @@ class TestThread < Test::Unit::TestCase assert_equal(t1, t3.value) ensure - t1.kill if t1 - t2.kill if t2 - t3.kill if t3 + t1&.kill&.join + t2&.kill&.join + t3&.kill&.join + end + + def test_join_argument_conversion + t = Thread.new {} + assert_raise(TypeError) {t.join(:foo)} + + limit = Struct.new(:to_f, :count).new(0.05) + assert_same(t, t.join(limit)) + end + + { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'], + 'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'], + 'INFINITY' => Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_#{name}") do + t = Thread.new {} + assert_same t, t.join(limit), "limit=#{limit.inspect}" + end + end + + { 'minus_1' => -1, + 'minus_0_1' => -0.1, + 'FIXNUM_MIN' => RbConfig::LIMITS['FIXNUM_MIN'], + 'INT64_MIN' => RbConfig::LIMITS['INT64_MIN'], + 'minus_INFINITY' => -Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_negative_#{name}") do + t = Thread.new { sleep } + begin + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(30) do + assert_nil t.join(limit), "limit=#{limit.inspect}" + end + end + ensure + t.kill + end + end end def test_kill_main_thread @@ -259,14 +317,14 @@ class TestThread < Test::Unit::TestCase s += 1 end Thread.pass until t.stop? + sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait assert_equal(1, s) t.wakeup Thread.pass while t.alive? assert_equal(2, s) assert_raise(ThreadError) { t.wakeup } - ensure - t.kill if t + t&.kill&.join end def test_stop @@ -305,7 +363,10 @@ class TestThread < Test::Unit::TestCase assert_in_out_err([], <<-INPUT, %w(false 1), []) p Thread.abort_on_exception begin - t = Thread.new { raise } + t = Thread.new { + Thread.current.report_on_exception = false + raise + } Thread.pass until t.stop? p 1 rescue @@ -317,7 +378,10 @@ class TestThread < Test::Unit::TestCase Thread.abort_on_exception = true p Thread.abort_on_exception begin - Thread.new { raise } + Thread.new { + Thread.current.report_on_exception = false + raise + } sleep 0.5 p 1 rescue @@ -340,7 +404,11 @@ class TestThread < Test::Unit::TestCase p Thread.abort_on_exception begin ok = false - t = Thread.new { Thread.pass until ok; raise } + t = Thread.new { + Thread.current.report_on_exception = false + Thread.pass until ok + raise + } t.abort_on_exception = true p t.abort_on_exception ok = 1 @@ -358,35 +426,40 @@ class TestThread < Test::Unit::TestCase q1 = Thread::Queue.new q2 = Thread::Queue.new - assert_equal(false, Thread.report_on_exception, - "global flags is false by default") - assert_equal(false, Thread.current.report_on_exception) + assert_equal(true, Thread.report_on_exception, + "global flag is true by default") + assert_equal(true, Thread.current.report_on_exception, + "the main thread has report_on_exception=true") - Thread.current.report_on_exception = true - assert_equal(false, + Thread.report_on_exception = true + Thread.current.report_on_exception = false + assert_equal(true, Thread.start {Thread.current.report_on_exception}.value, - "should not inherit from the parent thread") + "should not inherit from the parent thread but from the global flag") - assert_warn("", "exception should be ignored silently") { + assert_warn("", "exception should be ignored silently when false") { th = Thread.start { + Thread.current.report_on_exception = false q1.push(Thread.current.report_on_exception) raise "report 1" } assert_equal(false, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_warn(/report 2/, "exception should be reported") { + assert_warn(/report 2/, "exception should be reported when true") { th = Thread.start { q1.push(Thread.current.report_on_exception = true) raise "report 2" } assert_equal(true, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_equal(false, Thread.report_on_exception) assert_warn("", "the global flag should not affect already started threads") { + Thread.report_on_exception = false th = Thread.start { q2.pop q1.push(Thread.current.report_on_exception) @@ -395,19 +468,21 @@ class TestThread < Test::Unit::TestCase q2.push(Thread.report_on_exception = true) assert_equal(false, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_equal(true, Thread.report_on_exception) assert_warn(/report 4/, "should defaults to the global flag at the start") { + Thread.report_on_exception = true th = Thread.start { q1.push(Thread.current.report_on_exception) raise "report 4" } assert_equal(true, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_warn(/report 5/, "should defaults to the global flag at the start") { + assert_warn(/report 5/, "should first report and then raise with report_on_exception + abort_on_exception") { th = Thread.start { Thread.current.report_on_exception = true Thread.current.abort_on_exception = true @@ -418,12 +493,29 @@ class TestThread < Test::Unit::TestCase q2.push(true) Thread.pass while th.alive? } + assert_raise(RuntimeError) { th.join } } end; end + def test_ignore_deadlock + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "can't trap a signal from another process on Windows" + end + assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil) + p Thread.ignore_deadlock + q = Thread::Queue.new + trap(:INT){q.push :sig} + Thread.ignore_deadlock = true + p q.pop + INPUT + end + def test_status_and_stop_p - a = ::Thread.new { raise("die now") } + a = ::Thread.new { + Thread.current.report_on_exception = false + raise("die now") + } b = Thread.new { Thread.stop } c = Thread.new { Thread.exit } e = Thread.current @@ -442,11 +534,10 @@ class TestThread < Test::Unit::TestCase es1 = e.status es2 = e.stop? assert_equal(["run", false], [es1, es2]) - + assert_raise(RuntimeError) { a.join } ensure - a.kill if a - b.kill if b - c.kill if c + b&.kill&.join + c&.join end def test_switch_while_busy_loop @@ -464,24 +555,7 @@ class TestThread < Test::Unit::TestCase end assert(!flag, bug1402) ensure - waiter.kill.join - end - - def test_safe_level - ok = false - t = Thread.new do - EnvUtil.suppress_warning do - $SAFE = 1 - end - ok = true - sleep - end - Thread.pass until ok - assert_equal(0, Thread.current.safe_level) - assert_equal(1, t.safe_level) - - ensure - t.kill if t + waiter&.kill&.join end def test_thread_local @@ -501,17 +575,49 @@ class TestThread < Test::Unit::TestCase assert_equal([:foo, :bar, :baz].sort, t.keys.sort) ensure - t.kill if t + t&.kill&.join + end + + def test_thread_local_fetch + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + + x = nil + assert_equal("foo", t.fetch(:foo, 0)) + assert_equal("foo", t.fetch(:foo) {x = true}) + assert_nil(x) + assert_equal("foo", t.fetch("foo", 0)) + assert_equal("foo", t.fetch("foo") {x = true}) + assert_nil(x) + + x = nil + assert_equal(0, t.fetch(:qux, 0)) + assert_equal(1, t.fetch(:qux) {x = 1}) + assert_equal(1, x) + assert_equal(2, t.fetch("qux", 2)) + assert_equal(3, t.fetch("qux") {x = 3}) + assert_equal(3, x) + + e = assert_raise(KeyError) {t.fetch(:qux)} + assert_equal(:qux, e.key) + assert_equal(t, e.receiver) + ensure + t&.kill&.join end def test_thread_local_security - assert_raise(RuntimeError) do - Thread.new do - Thread.current[:foo] = :bar - Thread.current.freeze + Thread.new do + Thread.current[:foo] = :bar + Thread.current.freeze + assert_raise(FrozenError) do Thread.current[:foo] = :baz - end.join - end + end + end.join end def test_thread_local_dynamic_symbol @@ -531,7 +637,8 @@ class TestThread < Test::Unit::TestCase end Thread.pass until t.stop? assert_predicate(t, :alive?) - t.kill + ensure + t&.kill&.join end def test_mutex_deadlock @@ -560,11 +667,11 @@ class TestThread < Test::Unit::TestCase def test_mutex_illegal_unlock m = Thread::Mutex.new m.lock - assert_raise(ThreadError) do - Thread.new do + Thread.new do + assert_raise(ThreadError) do m.unlock - end.join - end + end + end.join end def test_mutex_fifo_like_lock @@ -632,14 +739,14 @@ class TestThread < Test::Unit::TestCase def make_handle_interrupt_test_thread1 flag r = [] - ready_p = false - done = false + ready_q = Thread::Queue.new + done_q = Thread::Queue.new th = Thread.new{ begin Thread.handle_interrupt(RuntimeError => flag){ begin - ready_p = true - sleep 0.01 until done + ready_q << true + done_q.pop rescue r << :c1 end @@ -648,10 +755,10 @@ class TestThread < Test::Unit::TestCase r << :c2 end } - Thread.pass until ready_p + ready_q.pop th.raise begin - done = true + done_q << true th.join rescue r << :c3 @@ -709,15 +816,16 @@ class TestThread < Test::Unit::TestCase end def test_handle_interrupt_blocking - r=:ng - e=Class.new(Exception) + r = nil + q = Thread::Queue.new + e = Class.new(Exception) th_s = Thread.current - begin - th = Thread.start{ + th = Thread.start { + assert_raise(RuntimeError) { Thread.handle_interrupt(Object => :on_blocking){ begin - Thread.pass until r == :wait - Thread.current.raise RuntimeError + q.pop + Thread.current.raise RuntimeError, "will raise in sleep" r = :ok sleep ensure @@ -725,27 +833,27 @@ class TestThread < Test::Unit::TestCase end } } - assert_raise(e) {r = :wait; sleep 0.2} - assert_raise(RuntimeError) {th.join(0.2)} - ensure - th.kill - end - assert_equal(:ok,r) + } + assert_raise(e) {q << true; th.join} + assert_equal(:ok, r) end def test_handle_interrupt_and_io assert_in_out_err([], <<-INPUT, %w(ok), []) th_waiting = true + q = Thread::Queue.new t = Thread.new { + Thread.current.report_on_exception = false Thread.handle_interrupt(RuntimeError => :on_blocking) { + q << true nil while th_waiting # async interrupt should be raised _before_ writing puts arguments puts "ng" } } - Thread.pass while t.stop? + q.pop t.raise RuntimeError th_waiting = false t.join rescue nil @@ -758,6 +866,7 @@ class TestThread < Test::Unit::TestCase th_waiting = false t = Thread.new { + Thread.current.report_on_exception = false Thread.handle_interrupt(RuntimeError => :on_blocking) { th_waiting = true nil while th_waiting @@ -783,15 +892,15 @@ class TestThread < Test::Unit::TestCase begin begin Thread.pass until done - rescue => e + rescue q.push :ng1 end begin Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt? - rescue RuntimeError => e + rescue RuntimeError q.push :ok end - rescue => e + rescue q.push :ng2 ensure q.push :ng3 @@ -859,15 +968,21 @@ _eom def test_thread_timer_and_interrupt bug5757 = '[ruby-dev:44985]' pid = nil - cmd = 'Signal.trap(:INT, "DEFAULT"); r,=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; r.read' + cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read' opt = {} opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM - s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, opt) do |in_p, out_p, err_p, cpid| + s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, **opt) do |in_p, out_p, err_p, cpid| + assert IO.select([out_p], nil, nil, 10), 'subprocess not ready' out_p.gets pid = cpid t0 = Time.now.to_f Process.kill(:SIGINT, pid) - Process.wait(pid) + begin + Timeout.timeout(10) { Process.wait(pid) } + rescue Timeout::Error + EnvUtil.terminate(pid) + raise + end t1 = Time.now.to_f [$?, t1 - t0, err_p.read] end @@ -914,16 +1029,15 @@ _eom end def test_thread_join_main_thread - assert_raise(ThreadError) do - Thread.new(Thread.current) {|t| + Thread.new(Thread.current) {|t| + assert_raise(ThreadError) do t.join - }.join - end + end + }.join end def test_main_thread_status_at_exit assert_in_out_err([], <<-'INPUT', ["false false aborting"], []) -require 'thread' q = Thread::Queue.new Thread.new(Thread.current) {|mth| begin @@ -963,31 +1077,30 @@ q.pop ary = [] t = Thread.new { - begin - ary << Thread.current.status - sleep #1 - ensure + assert_raise(RuntimeError) do begin ary << Thread.current.status - sleep #2 + sleep #1 ensure - ary << Thread.current.status + begin + ary << Thread.current.status + sleep #2 + ensure + ary << Thread.current.status + end end end } - begin - Thread.pass until ary.size >= 1 - Thread.pass until t.stop? - t.kill # wake up sleep #1 - Thread.pass until ary.size >= 2 - Thread.pass until t.stop? - t.raise "wakeup" # wake up sleep #2 - Thread.pass while t.alive? - assert_equal(ary, ["run", "aborting", "aborting"]) - ensure - t.join rescue nil - end + Thread.pass until ary.size >= 1 + Thread.pass until t.stop? + t.kill # wake up sleep #1 + Thread.pass until ary.size >= 2 + Thread.pass until t.stop? + t.raise "wakeup" # wake up sleep #2 + Thread.pass while t.alive? + assert_equal(ary, ["run", "aborting", "aborting"]) + t.join end def test_mutex_owned @@ -1014,7 +1127,7 @@ q.pop Thread.pass until mutex.locked? assert_equal(mutex.owned?, false) ensure - th.kill if th + th&.kill&.join end end @@ -1041,7 +1154,9 @@ q.pop env = {} env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size - out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + assert_not_predicate(status, :signaled?, err) + use_length ? out.length : out end @@ -1058,6 +1173,7 @@ q.pop "0 thread_machine_stack_size") assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size], "large thread_machine_stack_size") + assert_equal("ok", invoke_rec('print :ok', 1024 * 1024 * 100, nil, false)) end def test_vm_machine_stack_size @@ -1086,12 +1202,10 @@ q.pop bug8433 = '[ruby-core:55102] [Bug #8433]' mutex = Thread::Mutex.new - flag = false mutex.lock th = Thread.new do mutex.synchronize do - flag = true sleep end end @@ -1119,9 +1233,9 @@ q.pop end Process.wait2(f.pid) end - unless th.join(3) + unless th.join(EnvUtil.apply_timeout_scale(30)) Process.kill(:QUIT, f.pid) - Process.kill(:KILL, f.pid) unless th.join(1) + Process.kill(:KILL, f.pid) unless th.join(EnvUtil.apply_timeout_scale(1)) end _, status = th.value output = f.read @@ -1130,6 +1244,67 @@ q.pop assert_predicate(status, :success?, bug9751) end if Process.respond_to?(:fork) + def test_fork_value + bug18902 = "[Bug #18902]" + th = Thread.start { sleep 2 } + begin + pid = fork do + th.value + end + _, status = Process.wait2(pid) + assert_predicate(status, :success?, bug18902) + ensure + th.kill + end + end if Process.respond_to?(:fork) + + def test_fork_while_locked + m = Thread::Mutex.new + thrs = [] + 3.times do |i| + thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } } + end + thrs.each do |t| + assert_predicate t.value, :success?, '[ruby-core:85940] [Bug #14578]' + end + end if Process.respond_to?(:fork) + + def test_fork_while_parent_locked + skip 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + nr = 1 + thrs = [] + m.synchronize do + thrs = nr.times.map { Thread.new { m.synchronize {} } } + thrs.each { Thread.pass } + pid = fork do + m.locked? or exit!(2) + thrs = nr.times.map { Thread.new { m.synchronize {} } } + m.unlock + thrs.each { |t| t.join(1) == t or exit!(1) } + exit!(0) + end + _, st = Process.waitpid2(pid) + assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]' + end + thrs.each { |t| assert_same t, t.join(1) } + end + + def test_fork_while_mutex_locked_by_forker + skip 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + m.synchronize do + pid = fork do + exit!(2) unless m.locked? + m.unlock rescue exit!(3) + m.synchronize {} rescue exit!(4) + exit!(0) + end + _, st = Timeout.timeout(30) { Process.waitpid2(pid) } + assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]' + end + end + def test_subclass_no_initialize t = Module.new do break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") @@ -1183,13 +1358,93 @@ q.pop assert_equal("foo", c.new {Thread.current.name}.value, bug12290) end + def test_thread_native_thread_id + skip "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id) + assert_instance_of Integer, Thread.main.native_thread_id + + th1 = Thread.start{sleep} + + # newly created thread which doesn't run yet returns nil or integer + assert_include [NilClass, Integer], th1.native_thread_id.class + + Thread.pass until th1.stop? + + # After a thread starts (and execute `sleep`), it returns native_thread_id + assert_instance_of Integer, th1.native_thread_id + + th1.wakeup + Thread.pass while th1.alive? + + # dead thread returns nil + assert_nil th1.native_thread_id + end + def test_thread_interrupt_for_killed_thread - assert_normal_exit(<<-_end, '[Bug #8996]', timeout: 5, timeout_error: nil) + opts = { timeout: 5, timeout_error: nil } + + # prevent SIGABRT from slow shutdown with MJIT + opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + + assert_normal_exit(<<-_end, '[Bug #8996]', **opts) + Thread.report_on_exception = false trap(:TERM){exit} while true t = Thread.new{sleep 0} t.raise Interrupt + Thread.pass # allow t to finish end _end end + + def test_signal_at_join + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "can't trap a signal from another process on Windows" + # opt = {new_pgroup: true} + end + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120) + {# + n = 1000 + sig = :INT + trap(sig) {} + IO.popen([EnvUtil.rubybin, "-e", "#{<<~"{#1"}\n#{<<~'};#1'}"], "r+") do |f| + tpid = #{$$} + sig = :#{sig} + {#1 + STDOUT.sync = true + while gets + puts + Process.kill(sig, tpid) + end + };#1 + assert_nothing_raised do + n.times do + w = Thread.start do + sleep 30 + end + begin + f.puts + f.gets + ensure + w.kill + w.join + end + end + end + n.times do + w = Thread.start { sleep 30 } + begin + f.puts + f.gets + ensure + w.kill + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + w.join(30) + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + diff = t1 - t0 + assert_operator diff, :<=, 2 + end + end + end + }; + end end |
