KeboardInterrupt return code value

Hi,

I am building python form source using tarball, I have made some changes to the source code so that it can run on my platform. My platform is NonStop systems. When I try to run the following code my retcode returns a value of 9 whereas it’s ideal return value should be -2.

import subprocess
import sys
retcode = subprocess.call([sys.executable, "myscript.py"])
print(retcode)

myscript.py file has the following code

raise KeyboardInterrupt

any suggestions on where to start looking in order to debug this issue. I tried to debug in the subprocess call, the waitpid returns sts value of 2304 and then _handle_exit_status call returns a value of 9.

Retcodes are unsigned in posix.
So -2 is not allowed.

A negative number indicates that the process ended with a signal, and SIGINT is 2, so if a process ends with SIGINT, its return code will be -2. That part isn’t a problem.

And I’m not sure what’s going on in the OP’s code, because I just tried the same thing and I did get -2. Can you post the output of print(sys.version) please?

The following is the output of sys.version,

import sys
sys.version
'3.11.1 (main, Mar  7 2023, 13:36:52) [C]'

If tha’s sys.version, then the code you posted didn’t work. Can you post the ACTUAL code you tried, please, and the output?

That code is not valid Python code. print retcode is a SyntaxError in Python 3.

Either you are running different code, or you are using a very old version of Python, from the Python 1.x or 2.x series.

Sorry, it was a typo on my part, the correct code is

import subprocess
import sys
retcode = subprocess.call([sys.executable, "myscript.py"])
print(retcode)

the output is still the same, which is 9 in my case.

this is the output from my command line,

Python 3.11.1 (main, Mar  7 2023, 13:36:52) [C] on nonstop_kernel
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> import sys
>>> retcode = subprocess.call([sys.executable,"myscript.py"])
Traceback (most recent call last):
  File "/home/mydir/myscript.py", line 1, in <module>
    raise KeyboardInterrupt
KeyboardInterrupt
>>> print(retcode)
9
>>>

It would be more self-contained if you replace myscript.py with inline code. Do you get the same result with this?

retcode = subprocess.call([sys.executable,"-c", "raise KeyboardInterrupt"])

In any case, I still get -2, so maybe there’s a difference in OS. I’m doing this on Linux. What’s nonstop_kernel and how POSIX-compliant is it?

It is posix compliant to an extent(it’s legacy code so don’t know to what extent). My question being where to look for to start debugging, at which point of the program are signal handlers mapped? Does python use the default mapping or creates it’s own mapping for handling events? Where is the mapping set for handling of KeyboardInterrupt.

Split this into its two parts.

  1. From the shell Run python -c “raise KeyboardError”; echo $?
  2. Call a program, shell script, that returns the same exit code in the subprocess.call

From that you can figure where the problem is.

Not equivalent. The shell will translate negative return codes into values above 127 (in the case of SIGHUP, it will show 130), but returning these values is not the same as terminating with a signal.

In any case, this isn’t a Python problem; it seems to be a problem with the kernel’s handling of signals.

It might be interesting to compare these calls:

>>> subprocess.call([sys.executable, "-c", "import os; os._exit(9)"])
9
>>> subprocess.call([sys.executable, "-c", "import os; os._exit(-2)"])
254
>>> subprocess.call([sys.executable, "-c", "import os; os._exit(130)"])
130
>>> subprocess.call([sys.executable, "-c", "import os; os.kill(os.getpid(), 2)"])
Traceback (most recent call last):
  File "<string>", line 1, in <module>
KeyboardInterrupt
-2
>>> subprocess.call([sys.executable, "-c", "raise KeyboardInterrupt"])
Traceback (most recent call last):
  File "<string>", line 1, in <module>
KeyboardInterrupt
-2

That’s how they all show up on my Linux system. Note that I can exit with any 8-bit unsigned value, and negative numbers become positive numbers above 127; exiting 130 just exits 130 like you’d expect, and exiting -2 is exactly the same as exiting 254. But killing the process with 2 should exit with -2, and in this case, it reports KeyboardInterrupt and then does exactly that. Raising KeyboardInterrupt manually does the same thing. Sending different signals should also exit with negative numbers, although the default handling of a signal may affect this; for instance:

>>> subprocess.call([sys.executable, "-c", "import os; os.kill(os.getpid(), 17); print('Still here')"])
Still here
0
>>> subprocess.call([sys.executable, "-c", "import os; os.kill(os.getpid(), 15); print('Still here')"])
-15

This is because signal 17, SIGCHLD, doesn’t terminate the process. (And signal 19, SIGSTOP, won’t exit either way, as the process will stall out until it is CONTed.)

It may be of value to look at signal.valid_signals(), assuming you’re on Python 3.8 or newer. I’m not sure whether it’ll be truly indicative but it may have some information to reveal.

Thank you Chirs, Barry. The above information has shed some light on where I should look for. I will continue with this and see where it’s going and get back.

Yep its the fields that you extract with WIFSIGNALED macro.
Should have suggested writing a small C program to split out the nest use of python.

Yeah, a little C program would work. I’ve no idea how easy it is to spin up a tiny C program on the OP’s system though, so that might be a bit of a rabbit hole.

OP compiled python from source… a small C program should be easy.

Oh right, I forgot that LUL

The issue seems to be related to the fact that the script is raising a KeyboardInterrupt exception, and the subprocess.call() function is returning a value of 9 instead of the expected value of -2. Here’s what you can do to debug the issue:

  1. Check the documentation for the subprocess module to see what the return codes mean. According to the documentation, a return code of 9 means that the process was terminated by a signal.
  2. Modify the myscript.py file to catch the KeyboardInterrupt exception and return the expected value of -2. Here’s an example:

pythonCopy code

import signal

def signal_handler(sig, frame):
    raise KeyboardInterrupt

signal.signal(signal.SIGINT, signal_handler)

try:
    # Your code here
    pass
except KeyboardInterrupt:
    sys.exit(-2)

This code sets up a signal handler for the SIGINT signal, which is raised when the user presses Ctrl+C. When the signal is received, the signal_handler function is called, which raises a KeyboardInterrupt exception. The try-except block catches the exception and exits with a return code of -2.

  1. Use the PDB (Python Debugger) module to step through the code and see where the KeyboardInterrupt exception is being raised. Here’s an example:

pythonCopy code

import pdb

def main():
    # Your code here
    pdb.set_trace()

if __name__ == '__main__':
    main()

This code sets a breakpoint in the code using the set_trace() method from the PDB module. When the breakpoint is hit, the code stops executing and you can use the PDB commands to step through the code and see where the exception is being raised.

I hope these suggestions are helpful in debugging the issue. Let me know if you have any further questions.

Best regards,
Agata

Pylogix : https://pylogix.com/

GitHub: https://github.com/PYLOGiX-DEV/pylogix

Thank you Agata, will get back after trying this. I cross checked the valid_signals and saw that SIGINT is mapped to 2 itself. There must be some bitwise operation or some manipulation of signal bits that’s causing this issue. Working on it …

I tried the examine the issue, it’s not related to my platform which is NonStop. I tried Ctrl C on a sample C program while it was in sleep and got an exit status of 130 itself on my platform. I cross checked from the perspective of sample C program and found that behaviour on my platform is similar to Unix.

Then coming to python part even if I execute the command
raise KeyboardInterrupt

I am getting an exit status of 9, is it because the parent is killing the child before it handles SIGINT?

I checked the exit status using echo $? command.

Another interesting thing to note is even the command

signal.raise_signal(signal.SIGINT)

Is returning a return status of 9.

When I do a simple raise(SIGINT) in my sample C program it is returning a status of 130, so somewhere it is related to CPython that I am getting different status.

Any comments on how to resolve this?

You doubt you can compare bash exit status with what is calculated in the subprocess code.

The exit code is 16 bits I think. You have to split that 16 bits into fields to find out what it means.

man 2 waitpid details the macros that are defined to pull apart the wstatus value that has the return code from the dead process.

What you get as wstatus from a call to waitpid is not the same as the value that you can see with bash.

If you read the code is subprocess.py you can see that it uses os.waitpid and does a lot of work calculating the value to put in self.returncode. I see if using os.waitstatus_to_exitcode and other tests.

I recommend that you write two programs in C to investigate what is going on.

One that does the fork/exec of a process and does waitpid so that you can see the full wstatus after you do a normal exit, signal killed exit etc.

The other program, exit-test, that allows you exit in the way you want to test.

Then you can compare with running the same exit-test program using subprocess and compare results.

In this way you are using the OS system calls directly and the values are not being converted in ways that are leading to confusion.

I suggest you run tests on a linux system and see if that matches what non-stop is doing.