* 2022-01-18 [ci skip]
[ruby-80x24.org.git] / test / ruby / test_thread_cv.rb
blob88733419da14fd5c09921be72bd97c5e8415bcd1
1 # frozen_string_literal: false
2 require 'test/unit'
3 require 'tmpdir'
5 class TestThreadConditionVariable < Test::Unit::TestCase
6   ConditionVariable = Thread::ConditionVariable
7   Mutex = Thread::Mutex
9   def test_initialized
10     assert_raise(TypeError) {
11       ConditionVariable.allocate.wait(nil)
12     }
13   end
15   def test_condvar_signal_and_wait
16     mutex = Thread::Mutex.new
17     condvar = Thread::ConditionVariable.new
18     result = []
19     woken = nil
20     mutex.synchronize do
21       t = Thread.new do
22         mutex.synchronize do
23           result << 1
24           condvar.signal
25         end
26       end
28       result << 0
29       woken = condvar.wait(mutex)
30       result << 2
31       t.join
32     end
33     assert_equal([0, 1, 2], result)
34     assert(woken)
35   end
37   def test_condvar_wait_exception_handling
38     # Calling wait in the only thread running should raise a ThreadError of
39     # 'stopping only thread'
40     mutex = Thread::Mutex.new
41     condvar = Thread::ConditionVariable.new
43     locked = false
44     thread = Thread.new do
45       Thread.current.abort_on_exception = false
46       mutex.synchronize do
47         assert_raise(Interrupt) {
48           condvar.wait(mutex)
49         }
50         locked = mutex.locked?
51       end
52     end
54     until thread.stop?
55       sleep(0.1)
56     end
58     thread.raise Interrupt, "interrupt a dead condition variable"
59     thread.join
60     assert(locked)
61   end
63   def test_condvar_wait_and_broadcast
64     nr_threads = 3
65     threads = Array.new
66     mutex = Thread::Mutex.new
67     condvar = Thread::ConditionVariable.new
68     result = []
70     nr_threads.times do |i|
71       threads[i] = Thread.new do
72         mutex.synchronize do
73           result << "C1"
74           condvar.wait mutex
75           result << "C2"
76         end
77       end
78     end
79     sleep 0.1
80     mutex.synchronize do
81       result << "P1"
82       condvar.broadcast
83       result << "P2"
84     end
85     Timeout.timeout(5) do
86       nr_threads.times do |i|
87         threads[i].join
88       end
89     end
91     assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result
92   ensure
93     threads.each(&:kill)
94     threads.each(&:join)
95   end
97   def test_condvar_wait_deadlock
98     assert_in_out_err([], <<-INPUT, /\Afatal\nNo live threads left\. Deadlock/, [])
99       mutex = Thread::Mutex.new
100       cv = Thread::ConditionVariable.new
102       klass = nil
103       mesg = nil
104       begin
105         mutex.lock
106         cv.wait mutex
107         mutex.unlock
108       rescue Exception => e
109         klass = e.class
110         mesg = e.message
111       end
112       puts klass
113       print mesg
114 INPUT
115   end
117   def test_condvar_wait_deadlock_2
118     nr_threads = 3
119     threads = Array.new
120     mutex = Thread::Mutex.new
121     condvar = Thread::ConditionVariable.new
123     nr_threads.times do |i|
124       if (i != 0)
125         mutex.unlock
126       end
127       threads[i] = Thread.new do
128         mutex.synchronize do
129           condvar.wait mutex
130         end
131       end
132       mutex.lock
133     end
135     assert_raise(Timeout::Error) do
136       Timeout.timeout(0.1) { condvar.wait mutex }
137     end
138     mutex.unlock
139     threads.each(&:kill)
140     threads.each(&:join)
141   end
143   def test_condvar_timed_wait
144     mutex = Thread::Mutex.new
145     condvar = Thread::ConditionVariable.new
146     timeout = 0.3
147     locked = false
148     woken = true
150     t0 = Time.now
151     mutex.synchronize do
152       begin
153         woken = condvar.wait(mutex, timeout)
154       ensure
155         locked = mutex.locked?
156       end
157     end
158     t1 = Time.now
159     t = t1-t0
161     assert_operator(timeout*0.9, :<, t)
162     assert(locked)
163     assert_nil(woken)
164   end
166   def test_condvar_nolock
167     mutex = Thread::Mutex.new
168     condvar = Thread::ConditionVariable.new
170     assert_raise(ThreadError) {condvar.wait(mutex)}
171   end
173   def test_condvar_nolock_2
174     mutex = Thread::Mutex.new
175     condvar = Thread::ConditionVariable.new
177     Thread.new do
178       assert_raise(ThreadError) {condvar.wait(mutex)}
179     end.join
180   end
182   def test_condvar_nolock_3
183     mutex = Thread::Mutex.new
184     condvar = Thread::ConditionVariable.new
186     Thread.new do
187       assert_raise(ThreadError) {condvar.wait(mutex, 0.1)}
188     end.join
189   end
191   def test_condvar_empty_signal
192     mutex = Thread::Mutex.new
193     condvar = Thread::ConditionVariable.new
195     assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} }
196   end
198   def test_condvar_empty_broadcast
199     mutex = Thread::Mutex.new
200     condvar = Thread::ConditionVariable.new
202     assert_nothing_raised(Exception) { mutex.synchronize {condvar.broadcast} }
203   end
205   def test_dup
206     bug9440 = '[ruby-core:59961] [Bug #9440]'
207     condvar = Thread::ConditionVariable.new
208     assert_raise(NoMethodError, bug9440) do
209       condvar.dup
210     end
211   end
213   (DumpableCV = ConditionVariable.dup).class_eval {remove_method :marshal_dump}
215   def test_dump
216     bug9674 = '[ruby-core:61677] [Bug #9674]'
217     condvar = Thread::ConditionVariable.new
218     assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do
219       Marshal.dump(condvar)
220     end
222     condvar = DumpableCV.new
223     assert_raise(TypeError, bug9674) do
224       Marshal.dump(condvar)
225     end
226   end
228   def test_condvar_fork
229     mutex = Thread::Mutex.new
230     condvar = Thread::ConditionVariable.new
231     thrs = (1..10).map do
232       Thread.new { mutex.synchronize { condvar.wait(mutex) } }
233     end
234     thrs.each { 3.times { Thread.pass } }
235     pid = fork do
236       th = Thread.new do
237         mutex.synchronize { condvar.wait(mutex) }
238         :ok
239       end
240       until th.join(0.01)
241         mutex.synchronize { condvar.broadcast }
242       end
243       exit!(th.value == :ok ? 0 : 1)
244     end
245     _, s = Process.waitpid2(pid)
246     assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]'
247     until thrs.empty?
248       mutex.synchronize { condvar.broadcast }
249       thrs.delete_if { |t| t.join(0.01) }
250     end
251   end if Process.respond_to?(:fork)