diff options
Diffstat (limited to 'test/ruby/test_thread.rb')
| -rw-r--r-- | test/ruby/test_thread.rb | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb new file mode 100644 index 0000000000..135d17237e --- /dev/null +++ b/test/ruby/test_thread.rb @@ -0,0 +1,1317 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestThread < Test::Unit::TestCase + class Thread < ::Thread + Threads = [] + def self.new(*) + th = super + Threads << th + th + end + end + + def setup + Thread::Threads.clear + end + + def teardown + Thread::Threads.each do |t| + t.kill if t.alive? + begin + t.join + rescue Exception + end + end + end + + def test_inspect + 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) + 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 + + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + end + + def test_thread_variable_in_enumerator + Thread.new { + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + }.join + end + + def test_thread_variables + assert_equal [], Thread.new { Thread.current.thread_variables }.join.value + + t = Thread.new { + Thread.current.thread_variable_set(:foo, "bar") + Thread.current.thread_variables + } + assert_equal [:foo], t.join.value + end + + def test_thread_variable? + Thread.new { assert_not_send([Thread.current, :thread_variable?, "foo"]) }.value + t = Thread.new { + Thread.current.thread_variable_set("foo", "bar") + }.join + + assert_send([t, :thread_variable?, "foo"]) + assert_send([t, :thread_variable?, :foo]) + assert_not_send([t, :thread_variable?, :bar]) + end + + def test_thread_variable_strings_and_symbols_are_the_same_key + t = Thread.new {}.join + t.thread_variable_set("foo", "bar") + assert_equal "bar", t.thread_variable_get(:foo) + end + + def test_thread_variable_frozen + t = Thread.new { }.join + t.freeze + assert_raise(FrozenError) do + t.thread_variable_set(:foo, "bar") + end + end + + def test_mutex_synchronize + m = Thread::Mutex.new + r = 0 + num_threads = 10 + loop=100 + (1..num_threads).map{ + Thread.new{ + loop.times{ + m.synchronize{ + tmp = r + # empty and waste loop for making thread preemption + 100.times { + } + r = tmp + 1 + } + } + } + }.each{|e| + e.join + } + assert_equal(num_threads*loop, r) + end + + def test_mutex_synchronize_yields_no_block_params + bug8097 = '[ruby-core:53424] [Bug #8097]' + assert_empty(Thread::Mutex.new.synchronize {|*params| break params}, bug8097) + end + + def test_local_barrier + dir = File.dirname(__FILE__) + lbtest = File.join(dir, "lbtest.rb") + $:.unshift File.join(File.dirname(dir), 'ruby') + $:.shift + 3.times { + `#{EnvUtil.rubybin} #{lbtest}` + assert_not_predicate($?, :coredump?, '[ruby-dev:30653]') + } + end + + def test_priority + c1 = c2 = 0 + run = true + t1 = Thread.new { c1 += 1 while run } + t1.priority = 3 + t2 = Thread.new { c2 += 1 while run } + t2.priority = -3 + assert_equal(3, t1.priority) + assert_equal(-3, t2.priority) + sleep 0.5 + 5.times do + assert_not_predicate(t1, :stop?) + assert_not_predicate(t2, :stop?) + break if c1 > c2 + sleep 0.1 + end + run = false + t1.kill + t2.kill + assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + end + + def test_new + assert_raise(ThreadError) do + Thread.new + end + + t1 = Thread.new { sleep } + assert_raise(ThreadError) do + t1.instance_eval { initialize { } } + end + + t2 = Thread.new(&method(:sleep).to_proc) + assert_raise(ThreadError) do + t2.instance_eval { initialize { } } + end + + ensure + t1.kill if t1 + t2.kill if t2 + end + + def test_new_symbol_proc + bug = '[ruby-core:80147] [Bug #13313]' + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug) + begin; + exit("1" == Thread.start(1, &:to_s).value) + end; + end + + def test_join + t = Thread.new { sleep } + assert_nil(t.join(0.05)) + + ensure + t.kill if t + end + + def test_join2 + ok = false + t1 = Thread.new { ok = true; sleep } + Thread.pass until ok + Thread.pass until t1.stop? + t2 = Thread.new do + Thread.pass while ok + t1.join(0.01) + end + t3 = Thread.new do + ok = false + t1.join + end + assert_nil(t2.value) + t1.wakeup + assert_equal(t1, t3.value) + + ensure + t1.kill if t1 + t2.kill if t2 + t3.kill if t3 + end + + def test_kill_main_thread + assert_in_out_err([], <<-INPUT, %w(1), []) + p 1 + Thread.kill Thread.current + p 2 + INPUT + end + + def test_kill_wrong_argument + bug4367 = '[ruby-core:35086]' + assert_raise(TypeError, bug4367) { + Thread.kill(nil) + } + o = Object.new + assert_raise(TypeError, bug4367) { + Thread.kill(o) + } + end + + def test_kill_thread_subclass + c = Class.new(Thread) + t = c.new { sleep 10 } + assert_nothing_raised { Thread.kill(t) } + assert_equal(nil, t.value) + end + + def test_exit + s = 0 + Thread.new do + s += 1 + Thread.exit + s += 2 + end.join + assert_equal(1, s) + end + + def test_wakeup + s = 0 + t = Thread.new do + s += 1 + Thread.stop + s += 1 + end + Thread.pass until t.stop? + 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 + end + + def test_stop + assert_in_out_err([], <<-INPUT, %w(2), []) + begin + Thread.stop + p 1 + rescue ThreadError + p 2 + end + INPUT + end + + def test_list + assert_in_out_err([], <<-INPUT) do |r, e| + t1 = Thread.new { sleep } + Thread.pass + t2 = Thread.new { loop { Thread.pass } } + Thread.new { }.join + p [Thread.current, t1, t2].map{|t| t.object_id }.sort + p Thread.list.map{|t| t.object_id }.sort + INPUT + assert_equal(r.first, r.last) + assert_equal([], e) + end + end + + def test_main + assert_in_out_err([], <<-INPUT, %w(true false), []) + p Thread.main == Thread.current + Thread.new { p Thread.main == Thread.current }.join + INPUT + end + + def test_abort_on_exception + assert_in_out_err([], <<-INPUT, %w(false 1), []) + p Thread.abort_on_exception + begin + t = Thread.new { + Thread.current.report_on_exception = false + raise + } + Thread.pass until t.stop? + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(true 2), []) + Thread.abort_on_exception = true + p Thread.abort_on_exception + begin + Thread.new { + Thread.current.report_on_exception = false + raise + } + sleep 0.5 + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+") + p Thread.abort_on_exception + begin + t = Thread.new { raise } + Thread.pass until t.stop? + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(false true 2), []) + p Thread.abort_on_exception + begin + ok = false + 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 + sleep 1 + p 1 + rescue + p 2 + end + INPUT + end + + def test_report_on_exception + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + q1 = Thread::Queue.new + q2 = Thread::Queue.new + + 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.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 but from the global flag") + + 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_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_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) + raise "report 3" + } + q2.push(Thread.report_on_exception = true) + assert_equal(false, q1.pop) + Thread.pass while th.alive? + } + + 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_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 + q2.pop + raise "report 5" + } + assert_raise_with_message(RuntimeError, "report 5") { + q2.push(true) + Thread.pass while th.alive? + } + } + end; + end + + def test_status_and_stop_p + 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 + Thread.pass while a.alive? or !b.stop? or c.alive? + + assert_equal(nil, a.status) + assert_predicate(a, :stop?) + + assert_equal("sleep", b.status) + assert_predicate(b, :stop?) + + assert_equal(false, c.status) + assert_match(/^#<TestThread::Thread:.* dead>$/, c.inspect) + assert_predicate(c, :stop?) + + es1 = e.status + es2 = e.stop? + assert_equal(["run", false], [es1, es2]) + + ensure + a.kill if a + b.kill if b + c.kill if c + end + + def test_switch_while_busy_loop + bug1402 = "[ruby-dev:38319] [Bug #1402]" + flag = true + th = Thread.current + waiter = Thread.start { + sleep 0.1 + flag = false + sleep 1 + th.raise(bug1402) + } + assert_nothing_raised(RuntimeError, bug1402) do + nil while flag + 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 + end + + def test_thread_local + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + + assert_equal(true, t.key?(:foo)) + assert_equal(true, t.key?("foo")) + assert_equal(false, t.key?(:qux)) + assert_equal(false, t.key?("qux")) + + assert_equal([:foo, :bar, :baz].sort, t.keys.sort) + + ensure + t.kill if t + 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 if t + end + + def test_thread_local_security + Thread.new do + Thread.current[:foo] = :bar + Thread.current.freeze + assert_raise(FrozenError) do + Thread.current[:foo] = :baz + end + end.join + end + + def test_thread_local_dynamic_symbol + bug10667 = '[ruby-core:67185] [Bug #10667]' + t = Thread.new {}.join + key_str = "foo#{rand}" + key_sym = key_str.to_sym + t.thread_variable_set(key_str, "bar") + assert_equal("bar", t.thread_variable_get(key_str), "#{bug10667}: string key") + assert_equal("bar", t.thread_variable_get(key_sym), "#{bug10667}: symbol key") + end + + def test_select_wait + assert_nil(IO.select(nil, nil, nil, 0.001)) + t = Thread.new do + IO.select(nil, nil, nil, nil) + end + Thread.pass until t.stop? + assert_predicate(t, :alive?) + t.kill + end + + def test_mutex_deadlock + m = Thread::Mutex.new + m.synchronize do + assert_raise(ThreadError) do + m.synchronize do + assert(false) + end + end + end + end + + def test_mutex_interrupt + m = Thread::Mutex.new + m.lock + t = Thread.new do + m.lock + :foo + end + Thread.pass until t.stop? + t.kill + assert_nil(t.value) + end + + def test_mutex_illegal_unlock + m = Thread::Mutex.new + m.lock + Thread.new do + assert_raise(ThreadError) do + m.unlock + end + end.join + end + + def test_mutex_fifo_like_lock + m1 = Thread::Mutex.new + m2 = Thread::Mutex.new + m1.lock + m2.lock + m1.unlock + m2.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + + m3 = Thread::Mutex.new + m1.lock + m2.lock + m3.lock + m1.unlock + m2.unlock + m3.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + assert_equal(false, m3.locked?) + end + + def test_mutex_trylock + m = Thread::Mutex.new + assert_equal(true, m.try_lock) + assert_equal(false, m.try_lock, '[ruby-core:20943]') + + Thread.new{ + assert_equal(false, m.try_lock) + }.join + + m.unlock + end + + def test_recursive_outer + arr = [] + obj = Struct.new(:foo, :visited).new(arr, false) + arr << obj + def obj.hash + self[:visited] = true + super + raise "recursive_outer should short circuit intermediate calls" + end + assert_nothing_raised {arr.hash} + assert(obj[:visited], "obj.hash was not called") + end + + def test_thread_instance_variable + bug4389 = '[ruby-core:35192]' + assert_in_out_err([], <<-INPUT, %w(), [], bug4389) + class << Thread.current + @data = :data + end + INPUT + end + + def test_no_valid_cfp + skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE) + bug5083 = '[ruby-dev:44208]' + assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083) + assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083) + end + + def make_handle_interrupt_test_thread1 flag + r = [] + ready_q = Queue.new + done_q = Queue.new + th = Thread.new{ + begin + Thread.handle_interrupt(RuntimeError => flag){ + begin + ready_q << true + done_q.pop + rescue + r << :c1 + end + } + rescue + r << :c2 + end + } + ready_q.pop + th.raise + begin + done_q << true + th.join + rescue + r << :c3 + end + r + end + + def test_handle_interrupt + [[:never, :c2], + [:immediate, :c1], + [:on_blocking, :c1]].each{|(flag, c)| + assert_equal([flag, c], [flag] + make_handle_interrupt_test_thread1(flag)) + } + # TODO: complex cases are needed. + end + + def test_handle_interrupt_invalid_argument + assert_raise(ArgumentError) { + Thread.handle_interrupt(RuntimeError => :immediate) # no block + } + assert_raise(ArgumentError) { + Thread.handle_interrupt(RuntimeError => :xyzzy) {} + } + assert_raise(TypeError) { + Thread.handle_interrupt([]) {} # array + } + end + + def for_test_handle_interrupt_with_return + Thread.handle_interrupt(Object => :never){ + Thread.current.raise RuntimeError.new("have to be rescured") + return + } + rescue + end + + def test_handle_interrupt_with_return + assert_nothing_raised do + for_test_handle_interrupt_with_return + _dummy_for_check_ints=nil + end + end + + def test_handle_interrupt_with_break + assert_nothing_raised do + begin + Thread.handle_interrupt(Object => :never){ + Thread.current.raise RuntimeError.new("have to be rescured") + break + } + rescue + end + _dummy_for_check_ints=nil + end + end + + def test_handle_interrupt_blocking + r=:ng + e=Class.new(Exception) + th_s = Thread.current + th = Thread.start{ + assert_raise(RuntimeError) { + Thread.handle_interrupt(Object => :on_blocking){ + begin + Thread.pass until r == :wait + Thread.current.raise RuntimeError, "will raise in sleep" + r = :ok + sleep + ensure + th_s.raise e, "raise from ensure", $@ + end + } + } + } + assert_raise(e) {r = :wait; sleep 0.2} + th.join + assert_equal(:ok,r) + end + + def test_handle_interrupt_and_io + assert_in_out_err([], <<-INPUT, %w(ok), []) + th_waiting = true + + t = Thread.new { + Thread.current.report_on_exception = false + Thread.handle_interrupt(RuntimeError => :on_blocking) { + nil while th_waiting + # async interrupt should be raised _before_ writing puts arguments + puts "ng" + } + } + + Thread.pass while t.stop? + t.raise RuntimeError + th_waiting = false + t.join rescue nil + puts "ok" + INPUT + end + + def test_handle_interrupt_and_p + assert_in_out_err([], <<-INPUT, %w(:ok :ok), []) + 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 + # p shouldn't provide interruptible point + p :ok + p :ok + } + } + + Thread.pass until th_waiting + t.raise RuntimeError + th_waiting = false + t.join rescue nil + INPUT + end + + def test_handle_interrupted? + q = Thread::Queue.new + Thread.handle_interrupt(RuntimeError => :never){ + done = false + th = Thread.new{ + q.push :e + begin + begin + Thread.pass until done + rescue => e + q.push :ng1 + end + begin + Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt? + rescue RuntimeError => e + q.push :ok + end + rescue => e + q.push :ng2 + ensure + q.push :ng3 + end + } + q.pop + th.raise + done = true + th.join + assert_equal(:ok, q.pop) + } + end + + def test_thread_timer_and_ensure + assert_normal_exit(<<_eom, 'r36492', timeout: 10) + flag = false + t = Thread.new do + begin + sleep + ensure + 1 until flag + end + end + + Thread.pass until t.status == "sleep" + + t.kill + t.alive? == true + flag = true + t.join +_eom + end + + def test_uninitialized + c = Class.new(Thread) {def initialize; end} + assert_raise(ThreadError) { c.new.start } + + bug11959 = '[ruby-core:72732] [Bug #11959]' + + c = Class.new(Thread) {def initialize; exit; end} + assert_raise(ThreadError, bug11959) { c.new } + + c = Class.new(Thread) {def initialize; raise; end} + assert_raise(ThreadError, bug11959) { c.new } + + c = Class.new(Thread) { + def initialize + pending = pending_interrupt? + super {pending} + end + } + assert_equal(false, c.new.value, bug11959) + end + + def test_backtrace + Thread.new{ + assert_equal(Array, Thread.main.backtrace.class) + }.join + + t = Thread.new{} + t.join + assert_equal(nil, t.backtrace) + end + + 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' + 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| + out_p.gets + pid = cpid + t0 = Time.now.to_f + Process.kill(:SIGINT, pid) + Process.wait(pid) + t1 = Time.now.to_f + [$?, t1 - t0, err_p.read] + end + assert_equal(pid, s.pid, bug5757) + assert_equal([false, true, false, Signal.list["INT"]], + [s.exited?, s.signaled?, s.stopped?, s.termsig], + "[s.exited?, s.signaled?, s.stopped?, s.termsig]") + assert_include(0..2, t, bug5757) + end + + def test_thread_join_in_trap + assert_separately [], <<-'EOS' + Signal.trap(:INT, "DEFAULT") + t0 = Thread.current + assert_nothing_raised{ + t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$)} + + Signal.trap :INT do + t.join + end + + t.join + } + EOS + end + + def test_thread_value_in_trap + assert_separately [], <<-'EOS' + Signal.trap(:INT, "DEFAULT") + t0 = Thread.current + t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$); :normal_end} + + Signal.trap :INT do + t.value + end + assert_equal(:normal_end, t.value) + EOS + end + + def test_thread_join_current + assert_raise(ThreadError) do + Thread.current.join + end + end + + def test_thread_join_main_thread + Thread.new(Thread.current) {|t| + assert_raise(ThreadError) do + t.join + end + }.join + end + + def test_main_thread_status_at_exit + assert_in_out_err([], <<-'INPUT', ["false false aborting"], []) +q = Thread::Queue.new +Thread.new(Thread.current) {|mth| + begin + q.push nil + mth.run + Thread.pass until mth.stop? + p :mth_stopped # don't run if killed by rb_thread_terminate_all + ensure + puts "#{mth.alive?} #{mth.status} #{Thread.current.status}" + end +} +q.pop + INPUT + end + + def test_thread_status_in_trap + # when running trap handler, Thread#status must show "run" + # Even though interrupted from sleeping function + assert_in_out_err([], <<-INPUT, %w(sleep run), []) + Signal.trap(:INT) { + puts Thread.current.status + exit + } + t = Thread.current + + Thread.new(Thread.current) {|mth| + Thread.pass until t.stop? + puts mth.status + Process.kill(:INT, $$) + } + sleep 0.1 + INPUT + end + + # Bug #7450 + def test_thread_status_raise_after_kill + ary = [] + + t = Thread.new { + assert_raise(RuntimeError) do + begin + ary << Thread.current.status + sleep #1 + ensure + begin + ary << Thread.current.status + sleep #2 + ensure + ary << Thread.current.status + end + end + 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 + mutex = Thread::Mutex.new + + assert_equal(mutex.owned?, false) + mutex.synchronize { + # Now, I have the mutex + assert_equal(mutex.owned?, true) + } + assert_equal(mutex.owned?, false) + end + + def test_mutex_owned2 + begin + mutex = Thread::Mutex.new + th = Thread.new { + # lock forever + mutex.lock + sleep + } + + # acquired by another thread. + Thread.pass until mutex.locked? + assert_equal(mutex.owned?, false) + ensure + th.kill if th + end + end + + def test_mutex_unlock_on_trap + assert_in_out_err([], <<-INPUT, %w(locked unlocked false), []) + m = Thread::Mutex.new + + trapped = false + Signal.trap("INT") { |signo| + m.unlock + trapped = true + puts "unlocked" + } + + m.lock + puts "locked" + Process.kill("INT", $$) + Thread.pass until trapped + puts m.locked? + INPUT + end + + def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true + 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) + use_length ? out.length : out + end + + def test_stack_size + h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false)) + h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false)) + h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 10, 1024 * 1024 * 10, false)) + + assert_operator(h_default[:thread_vm_stack_size], :>, h_0[:thread_vm_stack_size], + "0 thread_vm_stack_size") + assert_operator(h_default[:thread_vm_stack_size], :<, h_large[:thread_vm_stack_size], + "large thread_vm_stack_size") + assert_operator(h_default[:thread_machine_stack_size], :>=, h_0[:thread_machine_stack_size], + "0 thread_machine_stack_size") + assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size], + "large thread_machine_stack_size") + end + + def test_vm_machine_stack_size + script = 'def rec; print "."; STDOUT.flush; rec; end; rec' + size_default = invoke_rec script, nil, nil + assert_operator(size_default, :>, 0, "default size") + size_0 = invoke_rec script, 0, nil + assert_operator(size_default, :>, size_0, "0 size") + size_large = invoke_rec script, 1024 * 1024 * 10, nil + assert_operator(size_default, :<, size_large, "large size") + end + + def test_machine_stack_size + # check machine stack size + # Note that machine stack size may not change size (depend on OSs) + script = 'def rec; print "."; STDOUT.flush; 1.times{1.times{1.times{rec}}}; end; Thread.new{rec}.join' + vm_stack_size = 1024 * 1024 + size_default = invoke_rec script, vm_stack_size, nil + size_0 = invoke_rec script, vm_stack_size, 0 + assert_operator(size_default, :>=, size_0, "0 size") + size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10 + assert_operator(size_default, :<=, size_large, "large size") + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_blocking_mutex_unlocked_on_fork + 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 + + Thread.pass until th.stop? + mutex.unlock + + pid = Process.fork do + exit(mutex.locked?) + end + + th.kill + + pid, status = Process.waitpid2(pid) + assert_equal(false, status.success?, bug8433) + end if Process.respond_to?(:fork) + + def test_fork_in_thread + bug9751 = '[ruby-core:62070] [Bug #9751]' + f = nil + th = Thread.start do + unless f = IO.popen("-") + STDERR.reopen(STDOUT) + exit + end + Process.wait2(f.pid) + end + unless th.join(EnvUtil.apply_timeout_scale(30)) + Process.kill(:QUIT, f.pid) + Process.kill(:KILL, f.pid) unless th.join(EnvUtil.apply_timeout_scale(1)) + end + _, status = th.value + output = f.read + f.close + assert_not_predicate(status, :signaled?, FailDesc[status, bug9751, output]) + assert_predicate(status, :success?, bug9751) + end if Process.respond_to?(:fork) + + def test_fork_while_locked + m = 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_subclass_no_initialize + t = Module.new do + break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") + end + t.class_eval do + def initialize + end + end + assert_raise_with_message(ThreadError, /C\u{30b9 30ec 30c3 30c9}/) do + t.new {} + end + end + + def test_thread_name + t = Thread.start {sleep} + sleep 0.001 until t.stop? + assert_nil t.name + s = t.inspect + t.name = 'foo' + assert_equal 'foo', t.name + t.name = nil + assert_nil t.name + assert_equal s, t.inspect + ensure + t.kill + t.join + end + + def test_thread_invalid_name + bug11756 = '[ruby-core:71774] [Bug #11756]' + t = Thread.start {} + assert_raise(ArgumentError, bug11756) {t.name = "foo\0bar"} + assert_raise(ArgumentError, bug11756) {t.name = "foo".encode(Encoding::UTF_32BE)} + ensure + t.kill + t.join + end + + def test_thread_invalid_object + bug11756 = '[ruby-core:71774] [Bug #11756]' + t = Thread.start {} + assert_raise(TypeError, bug11756) {t.name = []} + ensure + t.kill + t.join + end + + def test_thread_setname_in_initialize + bug12290 = '[ruby-core:74963] [Bug #12290]' + c = Class.new(Thread) {def initialize() self.name = "foo"; super; end} + assert_equal("foo", c.new {Thread.current.name}.value, bug12290) + end + + def test_thread_interrupt_for_killed_thread + assert_normal_exit(<<-_end, '[Bug #8996]', timeout: 5, timeout_error: nil) + Thread.report_on_exception = false + trap(:TERM){exit} + while true + t = Thread.new{sleep 0} + t.raise Interrupt + 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#{<<~'};'}") + {# + 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 |
