2

In a multi-threaded design, I want to do some clean steps when the program exits abnormally. The running thread should clean up the current task and then quit, rather than be killed immediately and leave some dirty data. I found that using threading module could not catch KeyInterrupt exception.

Here is my test code:

#!/usr/bin/env python3

from time import sleep

def do_sth():
    print("I'm doing something...")
    sleep(10)

if __name__ == "__main__":
    do_sth()

Python will raise KeyInterrupt exception when I press CTRL-c

$ Python3 test.py
I'm doing something ... 
^C
Traceback (most recent call last):
File "test.py", line 10, in <module>
do_sth ()
File "test.py", line 7, in do_sth
sleep (10)
KeyboardInterrupt

So I can catch this exception.

def do_sth ():
    try:
        print ("I'm doing something ...")
        sleep (10)
    except (KeyboardInterrupt, SystemExit):
        print ("I'm doing some clean steps and exit.")

But when I use threading module, this exception is not raised at all.

#!/usr/bin/env python3

from time import sleep
import threading

def do_sth():
    print("I'm doing something...")
    sleep(10)

if __name__ == '__main__':
    t = threading.Thread(target=do_sth)
    t.start()
    t.join()

result:

$ python3 test.py
I'm doing something...
^C

The running thread has been killed directly and no exception is raised.

How do I deal with this?

kmario23
  • 57,311
  • 13
  • 161
  • 150
Feng Yu
  • 1,431
  • 2
  • 18
  • 26
  • possible duplicate of [Python threading ignores KeyboardInterrupt exception](http://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception) – Ami Tavory Jun 06 '15 at 15:38

1 Answers1

0

One way is to handle KeyboardInterrupt exceptions.

Another thing to do in such scenarios is to manage the state of your application across all threads. One of the solutions is to add support for Signals in your code. It allows graceful handling of the shutting down of your process.

Here's one simple setup for that:

class SignalHandler:
    continue_running = True

    def __init__(self):
        signal.signal(signal.SIGUSR2, self.signal_handler)
        signal.signal(signal.SIGTERM, self.signal_handler)
        signal.signal(signal.SIGINT, self.signal_handler)
        logging.info("SignalHandler::Init")

    def signal_handler(self, num, stack):
        logging.warning('Received signal %d in %s' % (num, threading.currentThread()))
        SignalHandler.continue_running = False
        logging.warning("Time to SHUT DOWN ALL MODULES")

All threads would try and utilise the status from SignalHandler.continue_running to ensure that they all know when to stop. If somebody tried to kill this python process by calling kill -2 [PID] for example - all threads will come to know about the need to shut down.