Bug #20047
Updated by Eregon (Benoit Daloze) 12 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