If starting a subprocess from Python:
from subprocess import Popen
with Popen(['cat']) as p:
pass
is it possible that the process starts, but because of a KeyboardInterrupt caused by CTRL+C from the user, the as p bit never runs, but the process has indeed started? So there will be no variable in Python to do anything with the process, which means terminating it with Python code is impossible, so it will run until the end of the program?
Looking around the Python source, I see what I think is where the process starts in the __init__ call at https://github.com/python/cpython/blob/3.11/Lib/subprocess.py#L807 then on POSIX systems ends up at https://github.com/python/cpython/blob/3.11/Lib/subprocess.py#L1782C1-L1783C39 calling os.posix_spawn. What happens if there is a KeyboardInterrupt just after os.posix_spawn has completed, but before its return value has even been assigned to a variable?
Simulating this:
class FakeProcess():
def __init__(self):
# We create the process here at the OS-level,
# but just after, the user presses CTRL+C
raise KeyboardInterrupt()
def __enter__(self):
return self
def __exit__(self, _, __, ___):
# Never gets here
print("Got to exit")
p = None
try:
with FakeProcess() as p:
pass
finally:
print('p:', p)
This prints p: None, and does not print Got to exit.
This does suggest to me that a KeyboardInterrupt can prevent the cleanup of a process?
From https://docs.python.org/3/library/signal.html#note-on-signal-handlers-and-exception a KeyboardInterrupt is caused by Python's default SIGINT handler. And specifically it suggests when this can be raised:
So it can be raised between, but not during, any Python bytecode instruction. So it all boils down to is "calling a function" (
os.posix_spawnin this case) and "assigning its result to a variable" one instruction, or multiple.And it's multiple. From https://pl.python.org/docs/lib/bytecodes.html there are STORE_* instructions which are all separate from calling a function.
Which means that in this case, a process can be made at the OS level that Python doesn’t know about.
https://docs.python.org/3/library/signal.html#note-on-signal-handlers-and-exception also states
Which hints as to how, I think pervasive, albeit maybe rare in practice, such issues can be in Python.
But https://docs.python.org/3/library/signal.html#note-on-signal-handlers-and-exception also gives a way of avoiding this:
Which you can do if you really need/want to. Taking the answer from https://stackoverflow.com/a/76919499/1319998, which is itself based on https://stackoverflow.com/a/71330357/1319998 you can essentially defer SIGINT/the KeyboardInterrupt
to create a context manager with a stronger guarantee that you don't get a sort of zombie process due to a SIGINT during its creation:
which can be used as:
Or... instead of dealing with signal handlers, as https://stackoverflow.com/a/842567/1319998 suggests, you can start the process in a thread, which doesn't get interrupted by signals. But then you are left with having to deal with both inter thread and iter process communication.
used as
Choose your poison...