Signals not handled correctly
I need to prevent users from being able to exit my program via common signals (CTRL-C, CTRL-Z, etc). I have been able to get this working as expected in Python without any issues, however when compiled it no longer functions the same. The first signal (SIGINT/signal number 2, in this case) gets handled correctly, the second SIGINT, however, results in the program exiting after being handled. The output is below:
./signaltest
^C2
signal handled
^C2
signal handled
I've provided a minimal repro below.
python -m nuitka --version
1.5.5 Commercial: None Python: 3.9.2 (default, Feb 28 2021, 17:03:44) Flavor: Debian Python Executable: /usr/bin/python3 OS: Linux Arch: x86_64 Distribution: Debian 11 Version C compiler: /usr/bin/gcc (gcc).
- How did you install Nuitka and Python
pip
- Also supply a Short, Self Contained, Correct, Example
import signal
import time
def handle_signal(signal, frame):
print(signal)
print("signal handled")
signal.signal(signal.SIGINT, handle_signal)
while True:
time.sleep(5)
- Provide in your issue the Nuitka options used
python3 -m nuitka signal_test.py --onefile -o signaltest
The way that signal handling in Python works, is that when the SIGINT signal is received, it is not immediately delivered, but instead enqueued for later delivery in the form of an exception. Nuitka checks at the loop end, if an exception occurs, and then raises it, so standalone should be no issue.
The onefile bootstrap binary is handling SIGINT on its own. Aside of a grace time (which is an option), it terminates the program after that. You seem to not be using that option. It's 5s by default. I wonder if this affects your tests. Can you try that?
I also notice that it effectively is not easy to make it infinite, i.e. to disable the CTRL-C handling in onefile entirely. I would have to add that. Also, notice that then the onefile will only quit when it sees the child quit. I was considering if this means it could keep running, but I guess that is fine. If you report such, I might add "disable" as a value for the grace time. I feel like this has been asked before, maybe for another signal.
The default is to kill rogue programs by the onefile bootstrap. Not terminating after 5 seconds is considered bad. The SIGINT will e.g. be given by onefile bootstrap to the Python on user logout, and so on.
If I set --onefile-child-grace-time the program will still exit immediately if I CTRL-C twice. If I only CTRL-C once, the program will exit after the grace time.
Ok, it sounds like I will have to consider using standalone instead, then. Not ideal for my use case, but if that's the only way around this then that's fine.
Thanks for your help!
Well, it's not like this is not going to improve. However, I didn't reproduce what you say there, I could hit CTRL-C any amount of time and get outputs. Did you set it to a larger value? For me the 5s was enough to easily hit it 10 times and get the output before the hard kill.
Yes, I set it to 20 seconds. Here is how I compiled:
python3 -m nuitka signal_test.py --onefile -o signaltest --onefile-child-grace-time=20000
Two consecutive CTRL+C results in the program exiting immediately for me. I'm not sure why the behaviour doesn't match what you're seeing. I'm running debian inside VMware, but I don't believe that should make any difference to how signals are handled by the OS? It correctly catches the SIGINT, and the signal number is as expected... so I'm really not sure.
Ah, my bad. I tested on Windows. That must mean on Linux it's doing it wrong. I will check it out.
I reproduced it. Indeed the second signal is more deadly. This has to be a mistake in house the signal handling is done on Linux.
I think, what is happening, is that during signal handler it sleeps for 2s, 5s, or infinite, if you provide a large value, but then when the same signal arrives while it's being processed, the onefile handling then just does the default handling. The child Python process then quits seeing that exit. I will add a few traces to demonstrate that is happening.
What should happen instead is arguably that somehow a global state is changed, and the interrupt handler resumes, while having made sure that flag is seen by the main process. I think this is arguably a programming mistake to do that much in the signal handler as it does. Implementing said infinite feature is probably the best thing to do. But it would be nice to enforce also a grace time of 5s when we agreed on that and precisely that.