Project

General

Profile

Bug #20047

Updated by Eregon (Benoit Daloze) 5 months ago

```ruby 
 Signal.trap("INT") { p :SIGINT } 

 Thread.new do 
   sleep 0.6 
   `kill -INT #{$$}` 
 end 

 m, cv = Mutex.new, ConditionVariable.new 
 m.synchronize do 
   r = ARGV[0] ? cv.wait(m, 2) : cv.wait(m) 
   p ["ConditionVariable#wait returned", r] 
 end 
 ``` 

 The above program (without CLI arguments) should hang on `.wait` and not return, because neither ConditionVariable#{signal,broadcast} are used. return. 
 That's the behavior on TruffleRuby and JRuby, but not on CRuby, where `.wait` wakes up spuriously. 

 ``` 
 $ ruby -v spurious_cv.rb 
 ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux] 
 :SIGINT 
 ["ConditionVariable#wait returned", 1] 

 $ ruby -v spurious_cv.rb    
 truffleruby 23.1.1, like ruby 3.2.2, Oracle GraalVM Native [x86_64-linux] 
 :SIGINT 
 # hangs as expected 

 $ ruby -v spurious_cv.rb 
 jruby 9.4.5.0 (3.1.4) 2023-11-02 1abae2700f OpenJDK 64-Bit Server VM 17.0.8+7 on 17.0.8+7 +jit [x86_64-linux] 
 :SIGINT 
 # hangs as expected 
 ``` 

 When given an argument, it should wait 2 seconds. 
 But on CRuby it wakes up spuriously: 
 ``` 
 $ ruby -v spurious_cv.rb timeout 
 ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux] 
 :SIGINT 
 ["ConditionVariable#wait returned", 0] 

 $ ruby -v spurious_cv.rb timeout 
 truffleruby 23.1.1, like ruby 3.2.2, Oracle GraalVM Native [x86_64-linux] 
 :SIGINT 
 ["ConditionVariable#wait returned", #<ConditionVariable:0x188>] 

 $ ruby -v spurious_cv.rb timeout 
 jruby 9.4.5.0 (3.1.4) 2023-11-02 1abae2700f OpenJDK 64-Bit Server VM 17.0.8+7 on 17.0.8+7 +jit [x86_64-linux] 
 :SIGINT 
 ["ConditionVariable#wait returned", #<Thread::ConditionVariable:0x482ba4b1>] 
 ``` 

 `ConditionVariable#wait` needs to be interrupted to execute the signal handler, which does `{ p :SIGINT }` on the main thread. 
 However, `ConditionVariable#wait` should automatically be restarted internally after that, with the remaining timeout. 
 That is what I think is the bug in CRuby. 

 While it's good practice to have a loop around ConditionVariable#wait (at least when there is no timeout), it still seems highly unexpected in a high-level language like Ruby 
 to have ConditionVariable#wait return when neither ConditionVariable#{signal,broadcast} are used (i.e., spurious wakeups). 

 Also adding a loop is non-trivial for the case where a timeout argument is passed, as then one needs to manually account the remaining timeout instead of letting ConditionVariable#wait do its job correctly. 
 And also need to check that if ConditionVariable#wait returns nil then one should break the loop, which is quite error-prone. 
 Instead of just using `cv.wait(mutex, timeout)` when it works correctly. 

 From https://github.com/ruby-concurrency/concurrent-ruby/issues/1015

Back