summaryrefslogtreecommitdiff
path: root/test/ruby/test_thread.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_thread.rb')
-rw-r--r--test/ruby/test_thread.rb351
1 files changed, 302 insertions, 49 deletions
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 3283e30122..2a61fc3450 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -29,9 +29,19 @@ class TestThread < Test::Unit::TestCase
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)
+ m = Thread::Mutex.new
+ m.lock
+ line = __LINE__+1
+ th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do
+ m.synchronize {}
+ end
+ Thread.pass until th.stop?
+ s = th.inspect
+ assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:")
+ assert_include(s, " #{__FILE__}:#{line} ")
+ assert_equal(s, th.to_s)
ensure
+ m.unlock
th.join
end
@@ -226,9 +236,21 @@ class TestThread < Test::Unit::TestCase
assert_equal(t1, t3.value)
ensure
- t1&.kill
- t2&.kill
- t3&.kill
+ t1&.kill&.join
+ t2&.kill&.join
+ t3&.kill&.join
+ end
+
+ def test_join_argument_conversion
+ t = Thread.new {}
+
+ # Make sure that the thread terminates
+ Thread.pass while t.status
+
+ 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'],
@@ -305,7 +327,6 @@ class TestThread < Test::Unit::TestCase
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?
@@ -377,7 +398,7 @@ class TestThread < Test::Unit::TestCase
end
INPUT
- assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+")
+ assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), %r".+")
p Thread.abort_on_exception
begin
t = Thread.new { raise }
@@ -486,6 +507,19 @@ class TestThread < Test::Unit::TestCase
end;
end
+ def test_ignore_deadlock
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ omit "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 {
Thread.current.report_on_exception = false
@@ -533,23 +567,6 @@ class TestThread < Test::Unit::TestCase
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&.join
- end
-
def test_thread_local
t = Thread.new { sleep }
@@ -630,7 +647,7 @@ class TestThread < Test::Unit::TestCase
Thread.pass until t.stop?
assert_predicate(t, :alive?)
ensure
- t&.kill
+ t&.kill&.join
end
def test_mutex_deadlock
@@ -723,7 +740,7 @@ class TestThread < Test::Unit::TestCase
end
def test_no_valid_cfp
- skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
+ omit '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)
@@ -731,8 +748,8 @@ class TestThread < Test::Unit::TestCase
def make_handle_interrupt_test_thread1 flag
r = []
- ready_q = Queue.new
- done_q = Queue.new
+ ready_q = Thread::Queue.new
+ done_q = Thread::Queue.new
th = Thread.new{
begin
Thread.handle_interrupt(RuntimeError => flag){
@@ -808,14 +825,15 @@ 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
- th = Thread.start{
+ th = Thread.start {
assert_raise(RuntimeError) {
Thread.handle_interrupt(Object => :on_blocking){
begin
- Thread.pass until r == :wait
+ q.pop
Thread.current.raise RuntimeError, "will raise in sleep"
r = :ok
sleep
@@ -825,25 +843,26 @@ class TestThread < Test::Unit::TestCase
}
}
}
- assert_raise(e) {r = :wait; sleep 0.2}
- th.join
- 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
@@ -956,12 +975,14 @@ _eom
end
def test_thread_timer_and_interrupt
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5757 = '[ruby-dev:44985]'
pid = nil
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
@@ -1058,7 +1079,7 @@ q.pop
puts mth.status
Process.kill(:INT, $$)
}
- sleep 0.1
+ sleep
INPUT
end
@@ -1117,7 +1138,7 @@ q.pop
Thread.pass until mutex.locked?
assert_equal(mutex.owned?, false)
ensure
- th&.kill
+ th&.kill&.join
end
end
@@ -1144,7 +1165,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
@@ -1161,6 +1184,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
@@ -1231,8 +1255,23 @@ 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
+ th.join
+ end
+ end if Process.respond_to?(:fork)
+
def test_fork_while_locked
- m = Mutex.new
+ m = Thread::Mutex.new
thrs = []
3.times do |i|
thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } }
@@ -1243,7 +1282,7 @@ q.pop
end if Process.respond_to?(:fork)
def test_fork_while_parent_locked
- skip 'needs fork' unless Process.respond_to?(:fork)
+ omit 'needs fork' unless Process.respond_to?(:fork)
m = Thread::Mutex.new
nr = 1
thrs = []
@@ -1264,8 +1303,8 @@ q.pop
end
def test_fork_while_mutex_locked_by_forker
- skip 'needs fork' unless Process.respond_to?(:fork)
- m = Mutex.new
+ omit 'needs fork' unless Process.respond_to?(:fork)
+ m = Thread::Mutex.new
m.synchronize do
pid = fork do
exit!(2) unless m.locked?
@@ -1325,19 +1364,127 @@ q.pop
t.join
end
+ def test_yield_across_thread_through_enum
+ bug18649 = '[ruby-core:107980] [Bug #18649]'
+ @log = []
+
+ def self.p(arg)
+ @log << arg
+ end
+
+ def self.synchronize
+ yield
+ end
+
+ def self.execute(task)
+ success = true
+ value = reason = nil
+ end_sync = false
+
+ synchronize do
+ begin
+ p :before
+ value = task.call
+ p :never_reached
+ success = true
+ rescue StandardError => ex
+ ex = ex.class
+ p [:rescue, ex]
+ reason = ex
+ success = false
+ end
+
+ end_sync = true
+ p :end_sync
+ end
+
+ p :should_not_reach_here! unless end_sync
+ [success, value, reason]
+ end
+
+ def self.foo
+ Thread.new do
+ result = execute(-> { yield 42 })
+ p [:result, result]
+ end.join
+ end
+
+ value = to_enum(:foo).first
+ expected = [:before,
+ [:rescue, LocalJumpError],
+ :end_sync,
+ [:result, [false, nil, LocalJumpError]]]
+
+ assert_equal(expected, @log, bug18649)
+ assert_equal(42, value, bug18649)
+ 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_native_thread_id
+ omit "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
+ native_tid = th1.native_thread_id
+ assert_instance_of Integer, native_tid if native_tid # it can be nil
+
+ th1.wakeup
+ Thread.pass while th1.alive?
+
+ # dead thread returns nil
+ assert_nil th1.native_thread_id
+ end
+
+ def test_thread_native_thread_id_across_fork_on_linux
+ begin
+ require '-test-/thread/id'
+ rescue LoadError
+ omit "this test is only for Linux"
+ else
+ extend Bug::ThreadID
+ end
+
+ parent_thread_id = Thread.main.native_thread_id
+ real_parent_thread_id = gettid
+
+ assert_equal real_parent_thread_id, parent_thread_id
+
+ child_lines = nil
+ IO.popen('-') do |pipe|
+ if pipe
+ # parent
+ child_lines = pipe.read.lines
+ else
+ # child
+ puts Thread.main.native_thread_id
+ puts gettid
+ end
+ end
+ child_thread_id = child_lines[0].chomp.to_i
+ real_child_thread_id = child_lines[1].chomp.to_i
+
+ assert_equal real_child_thread_id, child_thread_id
+ refute_equal parent_thread_id, child_thread_id
+ end
+
def test_thread_interrupt_for_killed_thread
- opts = { timeout: 5, timeout_error: nil }
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
- # prevent SIGABRT from slow shutdown with MJIT
- opts[:reprieve] = 3 if RubyVM::MJIT.enabled?
+ opts = { timeout: 5, timeout_error: nil }
- assert_normal_exit(<<-_end, '[Bug #8996]', opts)
+ assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
Thread.report_on_exception = false
trap(:TERM){exit}
while true
@@ -1350,9 +1497,14 @@ q.pop
def test_signal_at_join
if /mswin|mingw/ =~ RUBY_PLATFORM
- skip "can't trap a signal from another process on Windows"
+ omit "can't trap a signal from another process on Windows"
# opt = {new_pgroup: true}
end
+
+ if /freebsd/ =~ RUBY_PLATFORM
+ omit "[Bug #18613]"
+ end
+
assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120)
{#
n = 1000
@@ -1399,4 +1551,105 @@ q.pop
end
};
end
+
+ def test_pending_interrupt?
+ t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } }
+ t.raise(StandardError)
+ assert_equal(true, t.pending_interrupt?)
+ assert_equal(true, t.pending_interrupt?(Exception))
+ assert_equal(false, t.pending_interrupt?(ArgumentError))
+ end
+
+ def test_deadlock_backtrace
+ bug21127 = '[ruby-core:120930] [Bug #21127]'
+
+ expected_stderr = [
+ /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/,
+ /2 threads, 2 sleeps current:\w+ main thread:\w+\n/,
+ /\* #<Thread:\w+ sleep_forever>\n/,
+ :*,
+ /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/,
+ :*,
+ /\* #<Thread:\w+ -:10 sleep_forever>\n/,
+ :*,
+ /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/,
+ :*,
+ ]
+
+ assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127)
+ def frame_for_deadlock_test_1
+ yield
+ end
+
+ def frame_for_deadlock_test_2
+ yield
+ end
+
+ q = Thread::Queue.new
+ t = Thread.new { frame_for_deadlock_test_1 { q.pop } }
+
+ frame_for_deadlock_test_2 { t.join }
+ INPUT
+ end
+
+ # [Bug #21342]
+ def test_unlock_locked_mutex_with_collected_fiber
+ bug21127 = '[ruby-core:120930] [Bug #21127]'
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 5.times do
+ m = Mutex.new
+ Thread.new do
+ m.synchronize do
+ end
+ end.join
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ end
+ end;
+ end
+
+ def test_unlock_locked_mutex_with_collected_fiber2
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ MUTEXES = []
+ 5.times do
+ m = Mutex.new
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ MUTEXES << m
+ end
+ 10.times do
+ MUTEXES.clear
+ GC.start
+ end
+ end;
+ end
+
+ def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ mutexes = 1000.times.map do
+ Mutex.new
+ end
+
+ mutexes.map do |m|
+ Fiber.new do
+ m.lock
+ end.resume
+ end
+
+ GC.start
+
+ 1000.times.map do
+ Fiber.new do
+ raise "FAILED!" if mutexes.any?(&:owned?)
+ end.resume
+ end
+ end;
+ end
end