-
-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
Python doesn't exit with proper resultcode on SIGINT #41078
Comments
If you kill a python process with SIGINT (e.g. control-c), it catches When exiting because of a KeyboardInterrupt, python ought to See also http://www.cons.org/cracauer/sigint.html for a more |
Logged In: YES Feel like writing a patch? |
I'll close this in a couple of weeks unless someone wants it kept open. |
Here's a patch that implements this behavior. It is too late in the 3.2 beta/rc cycle to get this into 3.2. Consider it for 3.3. I'd like a review. |
Looks good to me. Do you need the TODO(gps)'s in there after implementing the behavior described? |
|
I wonder whether there is a precedent of some system mapping SIGINT to
IMO, if we give the illusion that the interpreter was actually killed, |
Agreed. Plus that is easier to implement and what I did. I'll remove the left over TODO(gps) comments (oops) before this is
|
+ kill(getpid(), SIGINT); kill() doesn't exist on Windows: use raise() which is more portable and doesn't require a PID argument. We may need to do something on Windows for console applications: see SetConsoleCtrlHandler(), + self.assertEqual(returncode, -signal.SIGINT, I don't think that such test can pass on Windows. |
This covers the same ground as bpo-14229, which was rejected with the argument that re-raising the signal is “ugly” and bypasses some things that are done by the standard OS exit() function. |
Taking a renewed look at this 8 years later... I agree, re-triggering the signal with the SIG_DFL handler would prevent us from doing the existing interpreter shutdown cleanup if we did it too early which would be a behavior change other than the exit value correction. So we should delay the re-signaling kill(getpid(), SIGINT) call until we've completed that and are about to exit anyways. The code has moved around a lot since i generated this patch on a 3.2-ish tree so it'll take me a while to untangle what would need to go where to create a PR. Instead of kill(getpid(), SIGINT) or raise(SIGINT), we could just checking the _UnhandledKeyboardInterrupt flag we return our exit valye adjusting it to be the one a calling shell may be expecting to indicate a SIGINT. That goes against the advice of https://www.cons.org/cracauer/sigint.html but is likely to work. A question that leads to is what _is_ the correct value. On Linux the magic 130 happens to be (SIGINT + 128). Triggering the libc or kernel supplied SIG_DFL handler as a final act avoids us ever needing to know what possible mechanisms to indicate this to the parent process are preferred on a platform. (if we know it is merely an exit value we could capture that in to a #define with a configure.ac check if the + 128 trick were deemed too magical despite being what everyone likely implements, standardized or not) |
What do you think of using a short test program (ex: uses raise(SIGINT)) in ./configure to get the "default exit code" to define a constant, and then use the constant for exit() in Python? I dislike the idea of raising signals in general. The exact behavior of signals depends too much on the OS, it's hard to get it right. But having a configurable exit code looks safe and simple enough. |
I expect that'll work as desired and avoids the re-raising of the signal. Unless told otherwise I assume this should be a POSIX specific platform behavior. ie: no return value alteration due to an uncaught KeyboardInterrupt on the Windows API build. |
For Windows, the default console control handler calls ExitProcess(STATUS_CONTROL_C_EXIT). If CMD is waiting on an application that exits with STATUS_CONTROL_C_EXIT, it prints "^C" to indicate the process was killed by Ctrl+C. For example: >>> STATUS_CONTROL_C_EXIT = 0xC000013A - 2**32
>>> STATUS_CONTROL_C_EXIT
-1073741510
>>> sys.exit(STATUS_CONTROL_C_EXIT)
^C
Note that switching to SIG_DFL with raise(SIGINT) does not invoke the default console control handler in Windows. It just invokes the default raise() behavior, which is to call _exit(3). This exit status value of 3 is arbitrary and meaningless. |
This issue was reported because with the current behavior, "the script will not properly exit on C-c". Exiting with code 128+SIGINT will not fix this. Please re-raise the signal. |
I'm assuming the calling shell uses waitpid() and then WIFSIGNALED()? |
jwilk: Confirmed. The exit code is not enough, we must trigger the SIG_DFL handler. to reproduce: while true; do ./sig.py ; done with the attached sig.py. pass a parameter to sig.py to have it exit 130 instead of triggering SIG_DFL... |
Gregory's last example reminded me that CMD checks for STATUS_CONTROL_C_EXIT for more than simply printing "^C". It also breaks out of a FOR loop when interactive and prompts to continue when executing a batch script. Normally CMD also gets a console control event when the user presses Ctrl+C, so it knows about the Ctrl+C regardless of the child's exit status. One exception is when we start a process with a new console via CMD's |
New changeset 38f11cc by Gregory P. Smith in branch 'master': |
This is in for 3.8. On earlier or unpatched Python versions: application owners have a workaround f they do not mind skipping a clean application shutdown (destructors) on posix platforms: On Windows the workaround is easier without altering clean shutdown, catch KeyboardInterrupt and sys.exit(0xC000013A - 2**32). |
In my experience, Python is were impossible things happen. Would it be possible to write a better error message to explain what happened? I expect that if PyOS_setsig() fails, the user cannot do much for that. It may be a bug in the libc and so cannot be fixed easily. Maybe we should simply ignore the error? |
i'm curious if this _ever_ comes up so i'd like to leave it in at least through some betas. I don't believe it should be possible. If it is, it'd probably be a restricted execution environment that fails all signal() calls. all perror will do is emit an error message to that effect before continuing so we still exit with an error code as the final fallback. |
New changeset 06babb2 by Gregory P. Smith in branch 'master': |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: