-
Notifications
You must be signed in to change notification settings - Fork 5
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
Signal handling in main process changed after forking a new worker process #33
Comments
Also, it's interesting that we register |
One more important note: the Instantiating the worker process only initiates the problem, but further worker existence is not needed for this problem to occur. |
I think a systemd process should be blocking signals and using sd_event_add_signal() to get notifications (?): https://man7.org/linux/man-pages/man3/sd_event_signal_handler_t.3.html We should do something like: Line 31 in 855fb24
But update this: Line 75 in 855fb24
to use sd_event_add_signal(). I'll make the updates and test it out. |
Yes, exactly, systemd requires the signals to be blocked first before registering them with systemd.
Ah, sure, this should be cleaned up! However, my intention was to keep all systemd-related code in one place (one of SID's own libraries where it can wrap it). Currently, the calls to systemd lib fns is kept in resource.c (libsidresource) and log-target-journal.c (libsidlog) - just for us to be able to switch into using a different library in the future if there's an alternative for other environments (e.g. #32), making switching easier. We can possible try to move the Then, if we called: Line 196 in 855fb24
...we'd passed that within one of |
The forking-and-exiting only makes sense if we start the daemon manually from a terminal and not with systemd, right? I see the sid.service file starts it in the foreground. Currently the parent hits the select() and gets the SIGTERM from the child and exits without handling the signal. This leaves a defunct process until the child exits. For the child, we'd like it to field the SIGTERM and exit the event loop, right? I think we need to make some updates for that to work. This is probably a low priority issue, right? I picked it somewhat randomly. I'll look through the other issues to see where I might be able to help. Please let me know if you have any suggestions. |
Yes, indeed, if systemd is used, it's not necessary to daemonize the process, systemd will take care of that for us...
For now, yes, it was supposed to exit the event loop immediately. I'd like to enhance this eventually so that if SID receives SIGTERM, it will stop listening to any new requests and let all existing workers to finish before SID exits completely. There's one important thing to note when considering signal handlers through systemd lib. If we don't specify the handler for sid_resource_create_signal_event_source (and so the underlying sd_event_add_signal call), the default operation executed by systemd lib is to exit its event loop. If the custom handler is defined, systemd lib lets us handle everything, including possible exit of the event loop. In this case, we need to call sid_resource_exit_event_loop (and so the underlying sd_event_exit) to do so.
Relatively low, but it was very annoying so I'm happy it's being resolved now :) Sorry, I promised to open issues here on github for all the things I've hit and have written down in my notes. Somehow I didn't get to that, but will try to do that asap. |
Is this ok to close now? It is fixed by : #50 |
Sure! Closing... |
Hmm, interesting, after resolving #52, this still seems to be an issue even after we got rid of the direct signal registration in commit a07f331. When I try to send SIGTERM or SIGINT to main SID process after at least one worker is processed, the signal is ignored. So It looks like this call in the worker still influences the parent process somehow: sid/src/resource/worker-control.c Lines 709 to 710 in 6a4dbe2
|
Yeah, after the following in the log:
The parent process no longer responds to the signals. I can't find documentation that confirms that is how systemd is designed to work. I'll track down a mailing list or IRC channel to confirm what we are seeing is working as intended. |
I see that systemd doesn't want users to use the event loop after forking, this is a check they call on each We should be still OK though as we're not using the event loop from the parent in any children anymore (we create a new one in the worker). That's also the reason I'm using Lines 309 to 310 in 99b185d
The |
FYI - I added 👍
It does not get called in the parent. I tried to reproduce this issue with a smaller program: https://github.com/trgill/signal_test The does not reproduce the problem we are seeing. I'm in the process of making it closer to what sid does. |
With these changes: The signal is delivered to _sd_signal_event_handler() after the child processes have completed. The data variable isn't set right because I commented out the _create_event_source(). I think this is a clue about what is going wrong. I'm looking through _on_worker_proxy_child_event() which is called in the parent process. I'm wondering if there is a problem in the worker proxy. It isn't clear to me how the worker proxy lifetime is designed to work yet. |
Odd... I'm just looking at the code in the
The Anyway, once the worker is finished, the worker_proxy should be destroyed too - once the proxy receives signal from the child that has just exited, it destroys itself here: sid/src/resource/worker-control.c Line 1017 in 0d44fef
|
In _create_event_source() - we want to add the new_es->list or new_es?
|
The |
But we need to initialize the next and previous pointers, right?
|
Nevermind - I see. |
Well, it really looks like that the child process is fiddling with the parent's signal registrations! When I try to run this under gdb, then tracing the worker process calling this line: sid/src/resource/worker-control.c Lines 709 to 710 in 27ab566
...which in turn calls
So we're at There, Now, right at this point, if I look at
In the worker, there are 2 signalfds - one (presumably FD 9) is the inherited one, the other with FD 15 is a new one created in the worker for the worker's event loop (I suppose systemd creates one signalfd per event loop if signal events are registered). So I think we need to avoid this interference somehow... not sure yet how right at this moment, but we need to figure something out here if we want to do the proper cleanup in the worker and get rid of unused inherited stuff. |
Thanks for looking at this - I appreciate the help. I haven't been able to reproduce the behaviour with a simple test program; https://github.com/trgill/signal_test - I'm still looking to see why it is different. Please let me know what you think about avoiding the problem. Have you considered a thread pool rather than using fork()? |
...yes, that adds a bit of mystery to it :) I'll also try to look once again whether there's anything we're missing...
Honestly, I don't have a nice cure for this right at this moment - removing event sources completely before I'd like to keep the forking in there because that keeps the separate process for each run where module code is also executed and then there's the COW memory feature after forking we use - so we can use a snapshot of the in-memory database easily. With threads, this would require a completely different scheme, including more locking.... |
Sorry if I'm missing something obvious... just have a question. When I set a breakpoint on: sid/src/resource/worker-control.c Line 789 in 42a8157
I get a call stack of:
The new fork()'d (worker proxy) copy of the main process starts another main loop in the event dispatch of the top level event loop? The there is 1 worker proxy per worker process? The worker proxy collects the results from the worker and report back to the main process/database? |
The Now, if you're asking whether we call Some notes I have regarding this topic:
Exactly. The The main process actually consists of these resources (where necessary to avoid ambiguity, I inserted identifier in parenthesis):
The When a new worker is created, it has this resource tree:
That means, we reuse the inherited Then the rest is supposed to be destroyed - that is the The problem we have is with destroying I was playing with this a bit more and finally I managed to reproduced, I'll attach my reproducers in a while... |
I can reproduce the problem with the code attached below - it's pure In the forked process, I remove the INT and USR1 signals and only keep the TERM signal by calling its own The
However, it doesn't say a word about the This is the code for the reproducer (I've also tried your code with
|
This means |
Looking at the kernel code, it is clear that this is how signalfd is meant to work. Just like with a normal fd, the signalfd is copied on fork, but the underlying file is the same, which includes the data that's connected to that file with the signal mask. When the file is read, the kernel checks the pid, and returns the pending signals for that process, but there is still only one set of signal data associated with the file, and changing it from any process changes it for every process. I don't see any good way of using systemd's signal event source, since as you mentioned, there doesn't appear to be any way to clean it up without changing the signal mask of the parent. systemd is designed to not change the signal mask when you clear all the signals from the signalfd mask, but since there are still other signals being handled by signalfd when you start to remove the signal event sources, it changes the mask. Fortunately systemd has special code for handling cleaning up SIGCHLD signal, so that it doesn't change the mask for SIGCHLD in the parent when the child destroys a child event source. So as far as I can tell, the two easiest ways to fix this are:
The child will need to reset up the signal IO source, whichever we used. In general, any resource with it's own event loop probably needs a file descriptor for signals, either a signalfd, or an end of a pipe. Peter, does that make sense? Do you have any thoughts about how you'd like this solved? |
Yeah, looking at this from the perspective of FD, it makes sense... it's not special-cased...
Good point! :) This option looks neat and straightforward. I'd go with this one...
Yup! I'd patch the I think we can put the
There's also Then, though, there's a question related to this - if the parent didn't have the If we couldn't find a way to do the proper cleaning automatically and let the resource code decide on its own we could simply add an API function to |
(Hmm, just one question that comes to my mind is what happens when I create more than one singalfd FD with different masks? Signals are global per process, so I'm curious what happens here. I haven't tried that yet...) |
....well, yes, right, so it's a bit like "destroy all or nothing" for signals event sources then - looks we can't (or would be harder) to still keep the individual Lines 305 to 306 in 42a8157
...we need to destroy the signalfd in one go for all the signal event sources. Anyway, We may support "resource refresh" for child processes which would reinstate the event loop (== destroy old one and create a new one), but that's probably not needed right now. So patching that |
FYI - I put this PR together. I'm not sure it is ready, but it works. I need to re-read the thread above to make sure I got it right. |
Normally, SID daemon reacts to
SIGTERM
and it shuts itself down gracefully upon reception of this signal. However, if SID daemon forks at least one worker process (and that one can finish completely), the signal handler is not working correctly further - there's no reaction, the signal handler is not called at all.This seems to be related to the code in
worker_control_get_new_worker
where a new worker process is forked from daemon. After forking, in the worker process, we separate several resources we also need in the worker and we attach all of these inherited resources to top-level "worker" resource we've just created. Then we destroy the rest of unneeded resources in the worker process, including the "sid" resource which hadSIGTERM
andSIGINT
signals registered (and which was previously the top-level resource).Above-mentioned sequence is executed in worker-control code right here:
https://github.com/sid-project/sid-mvp/blob/0cd4698aa6d94d4b59cdbb892c689632d8572759/src/resource/worker-control.c#L137-L142
All works as expected (
SIGTERM
signal handling is OK) after commenting out this line: https://github.com/sid-project/sid-mvp/blob/0cd4698aa6d94d4b59cdbb892c689632d8572759/src/resource/worker-control.c#L142It looks that registering and then deregistering signal handlers (even though for various event loops) get messed up somehow...
Obviously, this is an issue when we're trying to stop SID daemon (...if that's
systemctl stop sid.service
, then the systemctl call halts because it is unable to stop the service - that will never stop as the signal is not processed).The text was updated successfully, but these errors were encountered: