New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-overriding signal handlers #2767
Non-overriding signal handlers #2767
Conversation
Ok, I'm working to cover the |
@nateberkopec hey, I've got a question here. First of all, the previous implementation (executing all previous traps before the latest one) is not our case. In Puma code, we override traps multiple times so we can't just turn off those overriding. But that'd be cool to have a mechanism to prepend custom application signal handlers before the Puma's ones, e.g. for debugging purposes like in the initial issue #2767 was requested. ( So here I created a small PoC how it could be done and I have done a few tests with a rails app and it kinda works. I let you decide now. |
@@ -482,15 +483,15 @@ def setup_signals | |||
end | |||
|
|||
begin | |||
Signal.trap "SIGINT" do | |||
Puma::Signal.trap "SIGINT" do | |||
stop | |||
end | |||
rescue Exception | |||
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exception handling could also be moved to Puma::Signal module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, I'll consider it if the solution makes sense at all 😄
So I guess the idea is that if I'm a Rails app developer, I'll have to use |
Someone recently submitted a similar type of patch so that Sidekiq's signal handler calls any previously registered trap: |
Also pointed out that Minitest has some code for this |
Maybe app developers should just be using something like this themselves. Since Puma itself doesn't need this functionality, and we can't do anything about people defining signal traps in their Rails initializers after Puma has already booted... Is there still a case where this functionality is useful in Puma itself? |
This functionality is not for Puma but mostly for app developers. Apparently, Puma get booted after Rails initializers and hence Puma overrides signal traps defined in initializers. Therefore, I thought maybe Puma shouldn't override them. Initially I peeked the sidekiq's solution but then I found that there are some cases in the code when we define a trap and then override it in another place. In those cases the sidekiq's solution (call the overrided trap in the overriding one) is not suitable because it makes it call all the overrided traps. E.g. in Rails initializers we have trap1, in Puma we have trap2 and trap3. In Puma's code trap3 is supposed to completely override trap2. But we want to save trap1. So using the sidekiq's solution we're expecting trap3 to be trap3+trap1. But the sidekiq's solution will make trap3 to run trap3+trap2+trap1. So I decided to let developers to inject their traps into Puma's ones. Maybe not the best solution but I'm certaintly open for discussion. |
For the case, when app's traps override Puma's ones - defenitely. But here I tried to solve the opposite problem - when Puma overrides app's traps. |
Looks like we have some test failures (the ones in |
@nateberkopec I know that some tests fail. I decided to first get your approval whether the idea has a reason to live. So if you think that we should implement what I've done, I'll fix and polish everything. |
@alexeevit 😓 Sorry I was unclear! Yes, I believe this makes sense and we should merge (when ready). |
if handler.respond_to?(:call) | ||
yield handler | ||
else | ||
# dirty hack to call string handlers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I'm stuck. First of all, I don't like this dirty hack. Besides that, it goes into an infinity loop on the SIGCHLD
, I don't know exactly why.
@MSP-Greg could I use your assistance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexeevit I'll have a look a bit later. Interesting failures, may have nothing to do with this PR. Not.sure.
Could you rebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Same problem as before... Sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? You thought the rebase could help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def custom_trap(sig, handler_str=nil, &handler)
new_handler = proc do
if handler.respond_to?(:call)
yield handler
else
# dirty hack to call string handlers
current_handler = Signal.trap(sig, handler_str)
Process.kill(sig, Process.pid)
Signal.trap(sig, new_handler) # or current_handler, same result
end
end
Signal.trap(sig, new_handler)
end
custom_trap 'CHLD', 'IGNORE'
Process.kill('CHLD', Process.pid)
In ruby 2.5 it just exits and in ruby 2.6 it goes into an infinite loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, I'd like to have a better way to invoke the string handlers for signals (e.g. DEFAULT, SYSTEM_DEFAULT, IGNORE, ...). But this behavior looks quite interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might have the explanation for the problems seen here with Ruby 2.6+, see #3313 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested the script in #2767 (comment) with Ruby 3.3, and it exited right away. This seems to suggest that the SIGCHLD
handler introduced in Ruby 2.6 via ruby/ruby@054a412 may indeed be the issue.
Note that I tested the script with the ruby_3_2
branch, and it still got stuck. This is a different bug than the one described in #3313, which is actually a Ruby bug already fixed in the ruby_3_2
branch: https://bugs.ruby-lang.org/issues/19837
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stanhu thanks for the info!
However, it still isn't clear, what's the expected fix here. Should we support the feature only for the ruby versions that have the bug fixed? Didn't dig into the nature of the bug, so not sure if there is a workaround for other ruby versions.
If the goal is to coexist with handlers added before or after puma adds one or more and for puma to not step on its own toes, I don't think the code to maintain the array of handlers is necessary. Ruby already maintains this array, one just needs to make sure to not overwrite it. This can be done by using the approach mentioned by @mperham above: from my PR, see discussion: Using the technique from this fantastic book, which was recently put online 🤩 https://workingwithruby.com/wwup/signals/#signal-handlers-are-global Maybe puma has special needs i'm not aware of (i don't know what "string handlers" are) but if not, I think this can be simplified so there is simply a reused method using a standard approach for each of these invocations. |
Hey! Yes, the goal is to coexist with handlers added before puma adds its own. However, as I wrote before, the solution mentioned by you and Mike doesn't fully meets our requirements:
We don't need the array, but we should save the user's traps and call them in Puma traps.
I don't really know if there's an official name for it. I meant the case, when you pass a string as the second argument to the |
Closing as "stuck". re: https://github.com/puma/puma/pull/2767/files#r872970473. If you (or anyone else) figures this one out, please reopen. |
@alexeevit thanks for the explanations - sorry for late response
I'm guessing this happens at boot time - maybe an array of lambdas or procs could be maintained during boot time, and then when done booting, register the handlers with ruby? If it's a case of a condition making puma say "actually the handler should be this" then should be easy to replace one with another in the array. If it's a case of a a subprocess and master process having different handlers, then maybe could still make it work - my "when done booting, register the handlers with ruby" step could be done independently for master and sub processes. Regarding https://github.com/puma/puma/pull/2767/files#r872970473 looking at
|
Description
Closes #2701
I found that Puma overrides all custom signal handlers, e.g. those that are defined in Rails initializers. Here's the implementation of safe signal handlers that allows having custom signal handlers in applications.
BTW, I'm not sure what tests I should add here so I'd be happy to get some directions.
Your checklist for this pull request
[ci skip]
to the title of the PR.#issue
" to the PR description or my commit messages.