Bash: Waiting on a background python process [duplicate] - python

This question already has an answer here:
Capturing SIGINT using KeyboardInterrupt exception works in terminal, not in script
(1 answer)
Closed 5 years ago.
I'm trying to:
launch a background process (a python script)
run some bash commands
Then send control-C to shut down the background process once the foreground tasks are finished
Minimal example of the what I've tried - Python test.py:
import sys
try:
print("Running")
while True:
pass
except KeyboardInterrupt:
print("Escape!")
Bash test.sh:
#!/bin/bash
python3 ./test.py &
pid=$!
# ... do something here ...
sleep 2
# Send an interrupt to the background process
# and wait for it to finish cleanly
echo "Shutdown"
kill -SIGINT $pid
wait
result=$?
echo $result
exit $result
But the bash script seems to be hanging on the wait and the SIGINT signal is not being sent to the python process.
I'm using Mac OS X, and am looking for a solution that works for bash on linux + mac.
Edit: Bash was sending interrupts but Python was not capturing them when being run as a background job. Fixed by adding the following to the Python script:
import signal
signal.signal(signal.SIGINT, signal.default_int_handler)

The point is SIGINT is used to terminate foreground process. You should directly use kill $pid to terminate background process.
BTW, kill $pid is equal to kill -15 $pid or kill -SIGTERM $pid.
Update
You can use signal module to deal with this situation.
import signal
import sys
def handle(signum, frame):
sys.exit(0)
signal.signal(signal.SIGINT, handle)
print("Running")
while True:
pass

Related

Python SIGINT handler not working with PM2 process monitoring

Hello i have created a script in python to run with PM2 a process monitoring tool available in NPM, the code is taken from the accepted answer of this question and is following
import signal
import sys
import time
def signal_handler(sig, frame):
print('You pressed Ctrl+C!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C to exit')
while True:
time.sleep(0.2)
NOTE: signal.pause() is not available for windows so i used infinite loop as an alternative (code blocking)
Now coming to the problem when I run the script manually from the CMD e.g., py test.py and then after pressing CTRL + C it captures the SIGINT perfectly, but when the same script is executed with PM2 e.g., pm2 start "py test.py" --name SigTestApp then stopping the PM2 process by typing pm2 stop SigTestApp simply kills the app and SIGINT is not being detected by the script
e.g., no message is shown "You pressed Ctrl+C!"

Sending a Ctrl+C SIGINT to sudoed C subprocess from python framework

I'm running a python subprocess using:
p = Popen(["sudo", "./a.out"])
where a.out is a C executable which runs continuously until a SIGINT or Ctrl+C signal is sent to it. I've had trouble with subprocess.Popen object functions such as send_signal() because Operation not permitted errors are raised due to the sudo nature of the executable. After this I tried to send a SIGINT to the subprocess via:
os.system(f"sudo kill -2 {p.pid}")
but this doesn't seem to target the process correctly. Running a quick sudo netstat -lpnt check shows the a.out process is still running on a pid which is different to the one which p.pid returned (usually by a few integers, i.e. p.pid returns 3031 but a.out is 3035). Anything that I've misunderstood?
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})

Logs from signal handler hidden when redirecting stdout to file via tee

I have a python program like this:
import signal, time
def cleanup(*_):
print("cleanup")
# do stuff ...
exit(1)
# trap ctrl+c and hide the traceback message
signal.signal(signal.SIGINT, cleanup)
time.sleep(20)
I run the program through a script:
#!/bin/bash
ARG1="$1"
trap cleanup INT TERM EXIT
cleanup() {
echo "\ncleaning up..."
killall -9 python >/dev/null 2>&1
killall -9 python3 >/dev/null 2>&1
# some more killing here ...
}
mystart() {
echo "starting..."
export PYTHONPATH=$(pwd)
python3 -u myfolder/myfile.py $ARG1 2>&1 | tee "myfolder/log.txt"
}
mystart &&
cleanup
My problem is that the message cleanup isn't appearing on the terminal nor on the log file.
However, if I call the program without redirecting the output it works fine.
If you don't want this to happen, put tee in the background so it isn't part of the process group getting a SIGINT. For example, with bash 4.1 or newer, you can start a process substitution with an automatically-allocated file descriptor providing a handle:
#!/usr/bin/env bash
# ^^^^ NOT /bin/sh; >(...) is a bashism, likewise automatic FD allocation.
exec {log_fd}> >(exec tee log.txt) # run this first as a separate command
python3 -u myfile >&"$log_fd" 2>&1 # then here, ctrl+c will only impact Python...
exec {log_fd}>&- # here we close the file & thus the copy of tee.
Of course, if you put those three commands in a script, that entire script becomes your foreground process, so different techniques are called for. Thus:
python3 -u myfile > >(trap '' INT; exec tee log.txt) 2>&1
Pressing ^C sends SIGINT to the entire foreground process group (the current pipeline or shell “job”), killing tee before it can write the output from your handler anywhere. You can use trap in the shell to immunize a command against SIGINT, although that comes with obvious risks.
Simply use the -i or --ignore-interrupts option of tee.
Documentation says:
-i, --ignore-interrupts
ignore interrupt signals
https://helpmanual.io/man1/tee/

Capturing SIGINT using KeyboardInterrupt exception works in terminal, not in script

I'm trying to catch SIGINT (or keyboard interrupt) in Python 2.7 program. This is how my Python test script test looks:
#!/usr/bin/python
import time
try:
time.sleep(100)
except KeyboardInterrupt:
pass
except:
print "error"
Next I have a shell script test.sh:
./test & pid=$!
sleep 1
kill -s 2 $pid
When I run the script with bash, or sh, or something bash test.sh, the Python process test stays running and is not killable with SIGINT. Whereas when I copy test.sh command and paste it into (bash) terminal, the Python process test shuts down.
I cannot get what's going on, which I'd like to understand. So, where is difference, and why?
This is not about how to catch SIGINT in Python! According to docs – this is the way, which should work:
Python installs a small number of signal handlers by default: SIGPIPE ... and SIGINT is translated into a KeyboardInterrupt exception
It is indeed catching KeyboardInterrupt when SIGINT is sent by kill if the program is started directly from shell, but when the program is started from bash script run on background, it seems that KeyboardInterrupt is never raised.
There is one case in which the default sigint handler is not installed at startup, and that is when the signal mask contains SIG_IGN for SIGINT at program startup. The code responsible for this can be found here.
The signal mask for ignored signals is inherited from the parent process, while handled signals are reset to SIG_DFL. So in case SIGINT was ignored the condition if (Handlers[SIGINT].func == DefaultHandler) in the source won't trigger and the default handler is not installed, python doesn't override the settings made by the parent process in this case.
So let's try to show the used signal handler in different situations:
# invocation from interactive shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))"
<built-in function default_int_handler>
# background job in interactive shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))" &
<built-in function default_int_handler>
# invocation in non interactive shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))"'
<built-in function default_int_handler>
# background job in non-interactive shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))" &'
1
So in the last example, SIGINT is set to 1 (SIG_IGN). This is the same as when you start a background job in a shell script, as those are non interactive by default (unless you use the -i option in the shebang).
So this is caused by the shell ignoring the signal when launching a background job in a non interactive shell session, not by python directly. At least bash and dash behave this way, I've not tried other shells.
There are two options to deal with this situation:
manually install the default signal handler:
import signal
signal.signal(signal.SIGINT, signal.default_int_handler)
add the -i option to the shebang of the shell script, e.g:
#!/bin/sh -i
edit: this behaviour is documented in the bash manual:
SIGNALS
...
When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.
which applies to non-interactive shells as they have job control disabled by default, and is actually specified in POSIX: Shell Command Language

Exit a Python process not kill it (via ssh)

I am starting my script locally via:
sudo python run.py remote
This script happens to also open a subprocess (if that matters)
webcam = subprocess.Popen('avconv -f video4linux2 -s 320x240 -r 20 -i /dev/video0 -an -metadata title="OfficeBot" -f flv rtmp://6f7528a4.fme.bambuser.com/b-fme/xxx', shell = True)
I want to know how to terminate this script when I SSH in.
I understand I can do:
sudo pkill -f "python run.py remote"
or use:
ps -f -C python
to find the process ID and kill it that way.
However none of these gracefully kill the process, I want to able to trigger the equilivent of CTRL/CMD C to register an exit command (I do lots of things on shutdown that aren't triggered when the process is simply killed).
Thank you!
You should use "signals" for it:
http://docs.python.org/2/library/signal.html
Example:
import signal, os
def handler(signum, frame):
print 'Signal handler called with signal', signum
signal.signal(signal.SIGINT, handler)
#do your stuff
then in terminal:
kill -INT $PID
or ctrl+c if your script is active in current shell
http://en.wikipedia.org/wiki/Unix_signal
also this might be useful:
How do you create a daemon in Python?
You can use signals for communicating with your process. If you want to emulate CTRL-C the signal is SIGINT (which you can raise by kill -INT and process id. You can also modify the behavior for SIGTERM which would make your program shut down cleanly under a broader range of circumstances.

Categories