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.rb1055
1 files changed, 852 insertions, 203 deletions
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index a05123f81d..2a61fc3450 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_relative 'envutil'
+require "rbconfig/sizeof"
+require "timeout"
class TestThread < Test::Unit::TestCase
class Thread < ::Thread
@@ -27,6 +28,36 @@ class TestThread < Test::Unit::TestCase
end
end
+ def test_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
+
+ 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
@@ -64,14 +95,14 @@ class TestThread < Test::Unit::TestCase
end
def test_thread_variable?
- refute Thread.new { Thread.current.thread_variable?("foo") }.join.value
+ Thread.new { assert_not_send([Thread.current, :thread_variable?, "foo"]) }.value
t = Thread.new {
Thread.current.thread_variable_set("foo", "bar")
}.join
- assert t.thread_variable?("foo")
- assert t.thread_variable?(:foo)
- refute t.thread_variable?(:bar)
+ 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
@@ -83,53 +114,43 @@ class TestThread < Test::Unit::TestCase
def test_thread_variable_frozen
t = Thread.new { }.join
t.freeze
- assert_raises(RuntimeError) do
+ assert_raise(FrozenError) do
t.thread_variable_set(:foo, "bar")
end
end
- def test_thread_variable_security
- t = Thread.new { sleep }
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
- end
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
- end
- end
-
def test_mutex_synchronize
- m = Mutex.new
+ m = Thread::Mutex.new
r = 0
- max = 10
- (1..max).map{
+ num_threads = 10
+ loop=100
+ (1..num_threads).map{
Thread.new{
- i=0
- while i<max*max
- i+=1
+ loop.times{
m.synchronize{
- r += 1
+ tmp = r
+ # empty and waste loop for making thread preemption
+ 100.times {
+ }
+ r = tmp + 1
}
- end
+ }
}
}.each{|e|
e.join
}
- assert_equal(max * max * max, r)
+ assert_equal(num_threads*loop, r)
end
def test_mutex_synchronize_yields_no_block_params
bug8097 = '[ruby-core:53424] [Bug #8097]'
- assert_empty(Mutex.new.synchronize {|*params| break params}, bug8097)
+ 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')
- require 'envutil'
$:.shift
3.times {
`#{EnvUtil.rubybin} #{lbtest}`
@@ -139,20 +160,26 @@ class TestThread < Test::Unit::TestCase
def test_priority
c1 = c2 = 0
- t1 = Thread.new { loop { c1 += 1 } }
+ run = true
+ t1 = Thread.new { c1 += 1 while run }
t1.priority = 3
- t2 = Thread.new { loop { c2 += 1 } }
+ 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
+ t1.join
+ t2.join
end
def test_new
@@ -171,34 +198,89 @@ 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
+ 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.5))
+ assert_nil(t.join(0.05))
ensure
- t.kill if t
+ t&.kill&.join
end
def test_join2
- t1 = Thread.new { sleep(1.5) }
+ ok = false
+ t1 = Thread.new { ok = true; sleep }
+ Thread.pass until ok
+ Thread.pass until t1.stop?
t2 = Thread.new do
- t1.join(1)
+ Thread.pass while ok
+ t1.join(0.01)
end
t3 = Thread.new do
- sleep 0.5
+ 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
+ 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'],
+ '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
@@ -244,15 +326,14 @@ class TestThread < Test::Unit::TestCase
Thread.stop
s += 1
end
- sleep 0.5
+ Thread.pass until t.stop?
assert_equal(1, s)
t.wakeup
- sleep 0.5
+ 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
@@ -270,7 +351,7 @@ class TestThread < Test::Unit::TestCase
assert_in_out_err([], <<-INPUT) do |r, e|
t1 = Thread.new { sleep }
Thread.pass
- t2 = Thread.new { loop { } }
+ 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
@@ -291,8 +372,11 @@ class TestThread < Test::Unit::TestCase
assert_in_out_err([], <<-INPUT, %w(false 1), [])
p Thread.abort_on_exception
begin
- Thread.new { raise }
- sleep 0.5
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise
+ }
+ Thread.pass until t.stop?
p 1
rescue
p 2
@@ -303,7 +387,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
@@ -311,11 +398,11 @@ 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
- Thread.new { raise }
- sleep 0.5
+ t = Thread.new { raise }
+ Thread.pass until t.stop?
p 1
rescue
p 2
@@ -325,9 +412,15 @@ class TestThread < Test::Unit::TestCase
assert_in_out_err([], <<-INPUT, %w(false true 2), [])
p Thread.abort_on_exception
begin
- t = Thread.new { sleep 0.5; raise }
+ 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
@@ -336,12 +429,106 @@ class TestThread < Test::Unit::TestCase
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_raise(RuntimeError) { th.join }
+ }
+
+ 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_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_raise(RuntimeError) { th.join }
+ }
+
+ 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 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?
+ }
+ assert_raise(RuntimeError) { th.join }
+ }
+ 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 { 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
- sleep 0.5
+ Thread.pass while a.alive? or !b.stop? or c.alive?
assert_equal(nil, a.status)
assert_predicate(a, :stop?)
@@ -356,11 +543,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
@@ -378,17 +564,7 @@ class TestThread < Test::Unit::TestCase
end
assert(!flag, bug1402)
ensure
- waiter.kill.join
- end
-
- def test_safe_level
- t = Thread.new { $SAFE = 3; sleep }
- sleep 0.5
- assert_equal(0, Thread.current.safe_level)
- assert_equal(3, t.safe_level)
-
- ensure
- t.kill if t
+ waiter&.kill&.join
end
def test_thread_local
@@ -405,43 +581,77 @@ class TestThread < Test::Unit::TestCase
assert_equal(false, t.key?(:qux))
assert_equal(false, t.key?("qux"))
- assert_equal([:foo, :bar, :baz], t.keys)
+ assert_equal([:foo, :bar, :baz].sort, t.keys.sort)
ensure
- t.kill if t
+ t&.kill&.join
end
- def test_thread_local_security
+ def test_thread_local_fetch
t = Thread.new { sleep }
- assert_raise(SecurityError) do
- Thread.new { $SAFE = 4; t[:foo] }.join
- end
+ assert_equal(false, t.key?(:foo))
- assert_raise(SecurityError) do
- Thread.new { $SAFE = 4; t[:foo] = :baz }.join
- end
+ t["foo"] = "foo"
+ t["bar"] = "bar"
+ t["baz"] = "baz"
- assert_raise(RuntimeError) do
- Thread.new do
- Thread.current[:foo] = :bar
- Thread.current.freeze
+ 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
+ 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
+ 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, 1))
+ assert_nil(IO.select(nil, nil, nil, 0.001))
t = Thread.new do
IO.select(nil, nil, nil, nil)
end
- sleep 0.5
- t.kill
+ Thread.pass until t.stop?
+ assert_predicate(t, :alive?)
+ ensure
+ t&.kill&.join
end
def test_mutex_deadlock
- m = Mutex.new
+ m = Thread::Mutex.new
m.synchronize do
assert_raise(ThreadError) do
m.synchronize do
@@ -452,30 +662,30 @@ class TestThread < Test::Unit::TestCase
end
def test_mutex_interrupt
- m = Mutex.new
+ m = Thread::Mutex.new
m.lock
t = Thread.new do
m.lock
:foo
end
- sleep 0.5
+ Thread.pass until t.stop?
t.kill
assert_nil(t.value)
end
def test_mutex_illegal_unlock
- m = Mutex.new
+ 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
- m1 = Mutex.new
- m2 = Mutex.new
+ m1 = Thread::Mutex.new
+ m2 = Thread::Mutex.new
m1.lock
m2.lock
m1.unlock
@@ -483,7 +693,7 @@ class TestThread < Test::Unit::TestCase
assert_equal(false, m1.locked?)
assert_equal(false, m2.locked?)
- m3 = Mutex.new
+ m3 = Thread::Mutex.new
m1.lock
m2.lock
m3.lock
@@ -496,7 +706,7 @@ class TestThread < Test::Unit::TestCase
end
def test_mutex_trylock
- m = Mutex.new
+ m = Thread::Mutex.new
assert_equal(true, m.try_lock)
assert_equal(false, m.try_lock, '[ruby-core:20943]')
@@ -530,24 +740,22 @@ class TestThread < Test::Unit::TestCase
end
def test_no_valid_cfp
- skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#intialize' 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)
- error = assert_raise(RuntimeError) do
- Thread.new(:to_s, &Module.method(:undef_method)).join
- end
- assert_equal("Can't call on top of Fiber or Thread", error.message, bug5083)
+ 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_p = 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.5
+ ready_q << true
+ done_q.pop
rescue
r << :c1
end
@@ -556,9 +764,10 @@ class TestThread < Test::Unit::TestCase
r << :c2
end
}
- Thread.pass until ready_p
+ ready_q.pop
th.raise
begin
+ done_q << true
th.join
rescue
r << :c3
@@ -616,43 +825,44 @@ 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.current.raise RuntimeError
- r=:ok
+ q.pop
+ Thread.current.raise RuntimeError, "will raise in sleep"
+ r = :ok
sleep
ensure
- th_s.raise e
+ th_s.raise e, "raise from ensure", $@
end
}
}
- sleep 1
- r=:ng
- th.raise RuntimeError
- th.join
- rescue e
- 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"
}
}
- sleep 0.1
+ q.pop
t.raise RuntimeError
th_waiting = false
t.join rescue nil
@@ -662,10 +872,12 @@ class TestThread < Test::Unit::TestCase
def test_handle_interrupt_and_p
assert_in_out_err([], <<-INPUT, %w(:ok :ok), [])
- th_waiting = true
+ 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
@@ -673,7 +885,7 @@ class TestThread < Test::Unit::TestCase
}
}
- sleep 0.1
+ Thread.pass until th_waiting
t.raise RuntimeError
th_waiting = false
t.join rescue nil
@@ -681,22 +893,23 @@ class TestThread < Test::Unit::TestCase
end
def test_handle_interrupted?
- q = Queue.new
+ q = Thread::Queue.new
Thread.handle_interrupt(RuntimeError => :never){
+ done = false
th = Thread.new{
q.push :e
begin
begin
- sleep 0.5
- rescue => e
+ Thread.pass until done
+ 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
@@ -704,13 +917,14 @@ class TestThread < Test::Unit::TestCase
}
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: 3)
+ assert_normal_exit(<<_eom, 'r36492', timeout: 10)
flag = false
t = Thread.new do
begin
@@ -730,9 +944,24 @@ _eom
end
def test_uninitialized
- c = Class.new(Thread)
- c.class_eval { def initialize; end }
+ 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
@@ -746,33 +975,41 @@ _eom
end
def test_thread_timer_and_interrupt
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5757 = '[ruby-dev:44985]'
- t0 = Time.now.to_f
pid = nil
- cmd = '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, _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)
- [$?, err_p.read]
+ 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
- t1 = Time.now.to_f
assert_equal(pid, s.pid, bug5757)
- unless /mswin|mingw/ =~ RUBY_PLATFORM
- # status of signal is not supported on Windows
- assert_equal([false, true, false, Signal.list["INT"]],
- [s.exited?, s.signaled?, s.stopped?, s.termsig],
- "[s.exited?, s.signaled?, s.stopped?, s.termsig]")
- end
- assert_in_delta(t1 - t0, 1, 1, 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{ sleep 0.2; Process.kill(:INT, $$) }
+ t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$)}
Signal.trap :INT do
t.join
@@ -780,42 +1017,50 @@ _eom
t.join
}
+ EOS
+ end
- assert_equal(:normal_end,
- begin
- t = Thread.new{ sleep 0.2; Process.kill(:INT, $$); :normal_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
- t.value
- end
- )
+ Signal.trap :INT do
+ t.value
+ end
+ assert_equal(:normal_end, t.value)
+ EOS
end
def test_thread_join_current
- assert_raises(ThreadError) do
+ assert_raise(ThreadError) do
Thread.current.join
end
end
def test_thread_join_main_thread
- assert_raises(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, %w(false), [])
+ assert_in_out_err([], <<-'INPUT', ["false false aborting"], [])
+q = Thread::Queue.new
Thread.new(Thread.current) {|mth|
begin
- sleep 0.1
+ q.push nil
+ mth.run
+ Thread.pass until mth.stop?
+ p :mth_stopped # don't run if killed by rb_thread_terminate_all
ensure
- p mth.alive?
+ puts "#{mth.alive?} #{mth.status} #{Thread.current.status}"
end
}
+q.pop
INPUT
end
@@ -825,14 +1070,16 @@ Thread.new(Thread.current) {|mth|
assert_in_out_err([], <<-INPUT, %w(sleep run), [])
Signal.trap(:INT) {
puts Thread.current.status
+ exit
}
+ t = Thread.current
Thread.new(Thread.current) {|mth|
- sleep 0.01
+ Thread.pass until t.stop?
puts mth.status
Process.kill(:INT, $$)
}
- sleep 0.1
+ sleep
INPUT
end
@@ -841,33 +1088,34 @@ Thread.new(Thread.current) {|mth|
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
- sleep 0.01
- t.kill # wake up sleep #1
- sleep 0.01
- t.raise "wakeup" # wake up sleep #2
- sleep 0.01
- 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
- mutex = Mutex.new
+ mutex = Thread::Mutex.new
assert_equal(mutex.owned?, false)
mutex.synchronize {
@@ -879,35 +1127,36 @@ Thread.new(Thread.current) {|mth|
def test_mutex_owned2
begin
- mutex = Mutex.new
+ mutex = Thread::Mutex.new
th = Thread.new {
# lock forever
mutex.lock
sleep
}
- sleep 0.01 until th.status == "sleep"
- # acquired another thread.
- assert_equal(mutex.locked?, true)
+ # acquired by another thread.
+ Thread.pass until mutex.locked?
assert_equal(mutex.owned?, false)
ensure
- th.kill if th
+ th&.kill&.join
end
end
def test_mutex_unlock_on_trap
assert_in_out_err([], <<-INPUT, %w(locked unlocked false), [])
- m = Mutex.new
+ m = Thread::Mutex.new
+ trapped = false
Signal.trap("INT") { |signo|
m.unlock
+ trapped = true
puts "unlocked"
}
m.lock
puts "locked"
Process.kill("INT", $$)
- sleep 0.2
+ Thread.pass until trapped
puts m.locked?
INPUT
end
@@ -916,7 +1165,9 @@ Thread.new(Thread.current) {|mth|
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, :timeout => 50)
+ out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true)
+ assert_not_predicate(status, :signaled?, err)
+
use_length ? out.length : out
end
@@ -933,8 +1184,10 @@ Thread.new(Thread.current) {|mth|
"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
- # check VM machine stack size
+ 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")
@@ -942,9 +1195,9 @@ Thread.new(Thread.current) {|mth|
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
- return if /mswin|mingw/ =~ RUBY_PLATFORM
-
+ 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'
@@ -954,18 +1207,16 @@ Thread.new(Thread.current) {|mth|
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
+ end unless /mswin|mingw/ =~ RUBY_PLATFORM
def test_blocking_mutex_unlocked_on_fork
bug8433 = '[ruby-core:55102] [Bug #8433]'
- mutex = Mutex.new
- flag = false
+ mutex = Thread::Mutex.new
mutex.lock
th = Thread.new do
mutex.synchronize do
- flag = true
sleep
end
end
@@ -993,9 +1244,9 @@ Thread.new(Thread.current) {|mth|
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
@@ -1003,4 +1254,402 @@ Thread.new(Thread.current) {|mth|
assert_not_predicate(status, :signaled?, FailDesc[status, bug9751, output])
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 = 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
+ omit '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
+ omit '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")
+ 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_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
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
+
+ opts = { timeout: 5, timeout_error: nil }
+
+ 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
+ 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
+ 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
+
+ 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