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