Send SIGINT to Python subprocess using os.kill as if pressing Ctrl+C
It's not working because you can't use os.kill
to send arbitrary signals on Windows:
os.kill(pid, sig)
Send signal sig to the process pid. Constants for the specific signals
available on the host platform are defined in the signal module.Windows: The
signal.CTRL_C_EVENT
andsignal.CTRL_BREAK_EVENT
signals
are special signals which can only be sent to console processes which
share a common console window, e.g., some subprocesses. Any other
value forsig
will cause the process to be unconditionally killed by
theTerminateProcess
API, and the exit code will be set to sig. The
Windows version ofkill()
additionally takes process handles to be
killed.
The only signals that can be sent via os.kill
are signal.CTRL_C_EVENT
and signal.CTRL_BREAK_EVENT
. Anything else just terminates the process, which is what is happening in your case. Using signal.CTRL_C_EVENT
won't work here, either, because processes started via multiprocessing.Process
aren't "console processes which share a common console window" with the parent. I'm not sure there's much you can do here with signals on Windows; It doesn't look like you're allowed to catch TerminateProcess
the way you can catch SIGTERM
on Unix, so you can't do any clean up prior to the process terminating, and you're not using a console application for the child, so signal.*_EVENT
won't work.
I think your options are: 1) Use the subprocess
module, and launch the child process with shell=True
, which I believe will mean signal.CTRL+C+EVENT
will work. 2) Stick with the multiprocessing
module, and use a "co-operative" method of interrupting the worker, like multiprocessing.Event
.
Sending a Ctrl+C SIGINT to sudoed C subprocess from python framework
You are actually getting pid of and killing sudo process (that forked your application process). Instead you should kill the whole process group with:
import subprocess, os
p = Popen(["sudo", "./a.out"])
pgid = os.getpgid(p.pid)
subprocess.check_output("sudo kill {}".format(pgid))
or with the help of pkill:
import subprocess
p = Popen(["sudo", "./a.out"])
subprocess.call(f"sudo pkill -2 -P {p.pid})
python forward SIGINT to subprocess
The code is in subprocess.run
, the function you called. SIGINT
gets converted into a KeyboardInterrupt
exception, which is handled by an except
clause that calls process.kill()
. You could implement different handling of SIGINT
using the signal
module. Notably, kill
doesn't send SIGINT
but sends SIGKILL
, so the subprocess does not get a chance to do similar handling with this default cleanup.
Python parent process is not catching SIGTERM/SIGINT signals when launching subprocess using os.sytem()
I think you'll have better luck with subprocess
than os.system
. In particular, I think you'll want to use subprocess
with shell=False
so that your child command is executed without a subshell (which might interfere with your ability to handle these kinds of signal-handling scenarios).
The code below does what you want, if I understand you correctly: CTRL-C causes both child and parent to stop; but if child dies for some other reason, parent will run the child again.
Here's a parent program similar to yours:
import signal
import subprocess
shutdown = False
def sigterm_handler(signum, frame):
print 'parent got shutdown'
global shutdown
shutdown = True
if __name__ == '__main__':
signal.signal(signal.SIGTERM, sigterm_handler)
signal.signal(signal.SIGINT, sigterm_handler)
cmd_args = ['python', 'child.py']
while not shutdown:
print 'executing', cmd_args
try:
subprocess.check_call(cmd_args)
except subprocess.CalledProcessError:
print 'child died'
pass
print 'Exiting Parent'
And here is a child program that runs for a while and then dies with a ZeroDivisionError.
import signal
import sys
import time
def sigterm_handler(signum, frame):
print 'child got shutdown'
sys.exit(0)
if __name__ == '__main__':
signal.signal(signal.SIGTERM, sigterm_handler)
signal.signal(signal.SIGINT, sigterm_handler)
for i in range(3, -1, -1):
print 'Child Process Running', i, i/i
time.sleep(3)
How do I capture SIGINT in Python?
Register your handler with signal.signal
like this:
#!/usr/bin/env python
import signal
import sys
def signal_handler(sig, frame):
print('You pressed Ctrl+C!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
signal.pause()
Code adapted from here.
More documentation on signal
can be found here.
python SIGINT not terminating calling shell
After considerable digging I found a few loosely related questions on Stack Overflow that eventually led me to an article describing the proper handling of SIGINT. (The most relevant section is How to be a proper program.)
From this information, I was able to solve the problem. Without it, I would have never have come close.
The solution is best illustrated by beginning with a bash script that cannot be terminated by a keyboard interrupt, but which does hide the ugly stack trace from Python's KeyboardInterrupt exception:
#!/usr/bin/env bash
echo "Press Ctrl-C to stop... No sorry it won't work."
while true
do
python -c '
import time, signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
time.sleep(100)
'
done
The change that makes the outer script process the interrupt is:
echo "Press Ctrl-C to stop..."
while true
do
python -c '
import time, signal, os
signal.signal(signal.SIGINT, signal.SIG_DFL)
time.sleep(100)
'
done
However, this solution makes it impossible to use a custom handler (for example, to perform cleanup). If doing so is required, then a more sophisticated approach is needed:
#!/usr/bin/env bash
echo "Press [CTRL+C] to stop ..."
while true
do
python -c '
import time, sys, signal, os
def handle_int(signum, frame):
# Cleanup code here
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
signal.signal(signal.SIGINT, handle_int)
time.sleep(100)
'
done
The reason appears to be that unless the inner process terminates through executing the default SIGINT handler provided by the system, the parent bash process does not realize that the child has terminated because of a keyboard interrupt, and does not itself terminate.
I have not fully understood all the ancillary issues quite yet, such as whether the parent process is not receiving the SIGINT from the system, or is receiving a signal, but ignoring it. I also have no idea what the default handler does or how the parent detects that it was called. If I am able to learn more, I will offer an update.
I must advance the question of whether the current behavior of Python should be considered a design flaw in Python. I have seen various manifestations of this issue over the years when calling Python from a shell script, but have not had the luxury of investigation until now. I have not found a single article through a web search, however, on the topic. If the issue does represent a flaw, it surprised me to observe that not many developers are affected.
Related Topics
How to Retrieve the Process Start Time (Or Uptime) in Python
Pip Error:'Module' Object Has No Attribute 'Cryptography_Has_Ssl_St'
Bash: Variable in Single Quote
How to See Pip Package Sizes Installed
Paramiko Error: Error Reading Ssh Protocol Banner
How to Make Shell Output Redirect (>) Write While Script Is Still Running
Python: Get Mount Point on Windows or Linux
Python: Complexity of Os.Path.Exists with a Ext4 Filesystem
Troubleshooting Oserror: Out of Pty Devices
Find "Home Directory" in Python
Installing Python 2.7 Without Root
Redirecting Python's Stdout to the File Fails with Unicodeencodeerror
Differencebetween C.Utf-8 and En_Us.Utf-8 Locales
Multiprocessing.Pool Spawning New Childern After Terminate() on Linux/Python2.7