1

When an exception occurs, I want to cleanly exit without partially processed data. This is the sort of code that I'm trying:

exit_lock = threading.lock()
def sig_interrupt_handler(signal, frame):
    with exit_lock:
        sys.exit(0)

signal.signal(signal.SIGINT, sig_interrupt_handler)

while data_left() > 0:
    with exit_lock:
        data = fetch_data(10)
        for datum in data:
            processor = Processor(datum)
            processor.process()
            datum.delete()

However, this code causes my program to lock up when ctrl+c is pressed. I've tried having the interrupt handler spin off a new thread, but this causes ctrl+c to do nothing.

What is the correct way to ensure a clean exit?

Jacklynn
  • 1,503
  • 1
  • 13
  • 23
  • Related: http://stackoverflow.com/questions/4205317/capture-keyboardinterrupt-in-python-without-try-except - This question helped me discover how to use the signal interrupt handler, but, if you need to defer exiting to a certain point in the code, it doesn't help. – Jacklynn Mar 06 '15 at 22:29
  • You might have more luck using `catch KeyboardInterrupt` rather than traditional signal handlers. – Tom Hunt Mar 06 '15 at 22:51
  • That was my first reaction - but when I catch KeyboardInterrupt, I have no way of knowing where my code was when the exception was thrown, and no way to resume it. – Jacklynn Mar 06 '15 at 22:54

1 Answers1

2

The problem you are facing, is that you are entering a deadlock. In your code, there is only one thread: the main application. It's holding a lock inside the while loop, and when you hit Ctrl+C, it will deadlock because the interrupt handler is waiting that the lock is released, which will never be released, as the application is locked in the interrupt handler and thus the while loop is not being executed.

You'll have more luck with something ressembling

do_process = True

def sig_interrupt_handler(signal, frame):
    global do_process
    do_process = False

signal.signal(signal.SIGINT, sig_interrupt_handler)

while data_left() > 0 and do_process:
    data = fetch_data(10)
    for datum in data:
        processor = Processor(datum)
        processor.process()
        datum.delete()

You can also have a look at the signal.alarm(time) function to ensure that the application terminates after a maximum time, or have something like

def sig_interrupt_handler(signal, frame):
    global do_process
    if do_process:
        do_process = False
    else:
        sys.exit(-1)

So that the user can hit Ctrl+C a second time to kill the process without waiting.

Cilyan
  • 7,883
  • 1
  • 29
  • 37
  • This code would be perfect - if the code that I put in my question was the only code that I was running. It isn't, so now I would need to add checks for the do_process variable in every other region of my code. – Jacklynn Mar 06 '15 at 22:52
  • 2
    This is a rough architecture, I can't guess what you don't disclose... The trick to understand is why the code proposed makes a deadlock. Solutions to circumvent this is very specific to your code (global variable to check, task manager, call of a cleanup function, ...). It's up to you. – Cilyan Mar 06 '15 at 23:08
  • That's the problem - it shouldn't be specific to my architecture. A proper solution would allow me to `ignore KeyboardInterrupt` for 3 lines and then re-raise it, without looking through the thousands of lines of code to understand how my one change will affect everything else. A good solution is 100% modular. – Jacklynn Mar 06 '15 at 23:18