1

What is the most efficient way (in terms of polling overhead) to request a Python program to stop (in a controlled way) from a Bash script. On python side I want a function (which executes as fast as possible) which returns true when a stop is requested or false if not. If true we save our work, release resources and exit.

For some simple tools I implemented the following:

  • In bash I do a touch /tmp/stop
  • My Python program polls on a frequent basis /tmp/stop does exist. If it exists if quits in a controlled way.
  • My bash script waits (loop - sleep - ps) until the related process is stopped.

This solution works, but polling for this file is most likely not the most efficient way.

Are there other options with less overhead (in terms of Python polling time)?

robert
  • 1,921
  • 2
  • 17
  • 27
  • 1
    Using a signal handler is your best option. Alternate options include using a secondary thread, that waits for some data on named pipe, or create a socket, that gets accessed by the bash process. – anishsane Apr 21 '16 at 05:50

2 Answers2

4

You could send an interrupt signal (SIGINT) to the python process. That's the same signal your shell would send when you hit Ctrl+C:

Looks like this in Bash:

python my_script.py &         # start the script in background
pyscript_pid=$!               # store the python interpreter' PID
sleep(5)                      # pause 5 seconds
kill -s SIGINT $pyscript_pid  # send the SIGINT signal to the process

And in Python you simply catch the KeyboardInterrupt exception that gets thrown when the interpreter receives the SIGINT signal:

try:
    print ("I'm still running...")
    # do something useful, but it must be interruptible at any time!

except KeyboardInterrupt:
    print ("I'm going to quit now.")

    # tidy up...
    # ... and exit

You should not do stuff that would break anything when interrupted half way inside the try block though, only perform stuff that can be interrupted or reset to the last valid state in the tidying up code. Alternatively you might use try: ... finally: ... to ensure code in the finally block will be started always, even if the code in the try gets interrupted while it's running.

You may also look at How do I capture SIGINT in Python? or @Robᵩ's answer to find out how to capture all possible signals and not only SIGINT and how to register event handler for them instead of using try-catch(-finally), but this here would be the simplest approach.

Community
  • 1
  • 1
Byte Commander
  • 6,506
  • 6
  • 44
  • 71
3

The UNIX signal mechanism would be an excellent choice. You don't need any temporary files, and the polling overhead is essentially zero.

You may shutdown the following python program gracefully like so: kill -USR1 $pid.

import signal
import time
import sys

please_stop = False
def setup_signal():
    def handler(x,y):
        global please_stop
        please_stop = True
    signal.signal(signal.SIGUSR1, handler)

def main_task():
    for i in range(10):
        print "Working hard on iteration #%d"%i
        time.sleep(1)
        if please_stop:
            print "Stopping now"
            sys.exit(0)

setup_signal()
main_task()
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Top, this is what I am looking for. – robert Apr 21 '16 at 06:00
  • An additional question: does KILL wait until the process is ended?. E.g. assume I start a save operation which takes 5-10 or 20 seconds, does kill wait that time? Then I do not need to poll in my bash. – robert Apr 21 '16 at 06:04
  • I am pretty sure it does not wait, because e.g. not all signals are defined to result in program termination, but check its manpage: `man kill` – Byte Commander Apr 21 '16 at 06:13
  • `kill` is a shell builtin in bash (check with `type kill`) so `man kill` might not be appropriate, `help kill` can be used in bash. Also see `man 2 sigaction` and on Linux `man 7 signal`. – cdarke Apr 21 '16 at 09:39