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