# -*- coding: us-ascii -*- # frozen_string_literal: false require 'test/unit' require "rbconfig/sizeof" require "timeout" 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 { '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 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? sleep 1 if 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 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(/^#$/, 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($SAFE, Thread.current.safe_level) assert_equal($SAFE, t.safe_level) ensure $SAFE = 0 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#{<<~'};'}", 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