
Issue #19473 has been updated by Eregon (Benoit Daloze). mame (Yusuke Endoh) wrote in #note-25:
Indeed, creating a new thread for each signal arrival is extremely simple.
It feels like a hack though and it's a pretty high cost to create a new Thread for every signal. If this was the official solution, then the `can't be called from trap context (ThreadError)` should mention it, but I think it's not good enough to be an official solution, just a hacky workaround. It seems nobody mentioned this workaround on https://github.com/ruby/timeout/issues/17. I think that's because it's unnatural and not a proper solution. BTW another problem with that workaround is if the same signal is sent twice in quick succession (e.g. two Ctrl+C) the handlers will run concurrently in two threads, which is a recipe for complicated problems. Currently trap handlers run serially (see issue description) and that seems clearly safer.
Setting up a single, dedicated thread to execute signal handlers serially is more complex, but it still doesn't require a lot of code.
The problem there is then all `trap` handlers need to `Q << it` which is too difficult to guarantee in general.
I suppose it would be for cases where you want to force a trap handler written by someone else (e.g., in a gem) to run in a separate thread. But I wonder, is such a scenario realistic?
Yes, for example gems like webservers like Puma set trap handlers. There are many gems with trap handlers: ``` $ gem-codesearch '\bSignal\.trap\b' | wc -l 5528 ``` (more with `\btrap\b` but that takes too long to list) ---------------------------------------- Bug #19473: can't be called from trap context (ThreadError) is too limiting https://bugs.ruby-lang.org/issues/19473#change-113974 * Author: Eregon (Benoit Daloze) * Status: Open * ruby -v: ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- Simple reproducer: ``` $ ruby -ve 'm=Mutex.new; trap(:HUP) { m.synchronize { p :OK } }; Process.kill :HUP, Process.pid; sleep 0.1' ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] -e:1:in `synchronize': can't be called from trap context (ThreadError) from -e:1:in `block in <main>' from -e:1:in `kill' from -e:1:in `<main>' ``` Expected behavior: ``` $ ruby -ve 'm=Mutex.new; trap(:HUP) { m.synchronize { p :OK } }; Process.kill :HUP, Process.pid; sleep 0.1' truffleruby 22.3.1, like ruby 3.0.3, GraalVM CE Native [x86_64-linux] :OK $ ruby -ve 'm=Mutex.new; trap(:HUP) { m.synchronize { p :OK } }; Process.kill :HUP, Process.pid; sleep 0.1' jruby 9.4.0.0 (3.1.0) 2022-11-23 95c0ec159f OpenJDK 64-Bit Server VM 17.0.6+10 on 17.0.6+10 +jit [x86_64-linux] :OK ``` This exception is highly problematic, for instance it breaks `Timeout.timeout` in `trap`: https://github.com/ruby/timeout/issues/17#issuecomment-1142035939 I suppose this behavior is because *sometimes* it's problematic to lock a Mutex in trap, e.g., if it's already locked by the main thread/fiber. But that would otherwise already raise `deadlock; recursive locking (ThreadError)`, so there is no point to fail early. And that's just one case, not all, so we should not always raise an exception. There seems to be no valid reason to prevent *all* `Mutex#synchronize` in `trap`. After all, if the Mutex for instance is only used in `trap`, it's well-defined AFAIK. For instance a given trap handler does not seem executed concurrently: ``` $ ruby -ve 'trap(:HUP) { puts "in trap\n"+caller.join("\n")+"\n\n"; sleep 0.1 }; pid = Process.pid; Process.wait fork { 20.times { Process.kill :HUP, pid } }; sleep 1' ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] in trap -e:1:in `wait' -e:1:in `<main>' in trap -e:1:in `wait' -e:1:in `<main>' in trap -e:1:in `wait' -e:1:in `<main>' in trap -e:1:in `wait' -e:1:in `<main>' in trap -e:1:in `wait' -e:1:in `<main>' in trap -e:1:in `wait' -e:1:in `<main>' ``` And if the trap handler using the Mutex is never called while the Mutex is held by the main thread/fiber, there is also no problem. -- https://bugs.ruby-lang.org/