Interrupting Python raw_input() in a child thread with ^C/KeyboardInterrupt
Asked Answered
C

2

8

In a multithreaded Python program, one thread sometimes asks for console input using the built-in raw_input(). I'd like to be able to be able to close the program while at a raw_input prompt by typing ^C at the shell (i.e., with a SIGINT signal). However, when the child thread is executing raw_input, typing ^C does nothing -- the KeyboardInterrupt is not raised until I hit return (leaving raw_input).

For example, in the following program:

import threading

class T(threading.Thread):
    def run(self):
        x = raw_input()
        print x

if __name__ == '__main__':
    t = T()
    t.start()
    t.join()

Typing ^C does nothing until after the input is finished. However, if we just call T().run() (i.e., the single-threaded case: just run raw_input in the main thread), ^C closes the program immediately.

Presumably, this is because SIGINT is sent to the main thread, which is suspended (waiting for the GIL) while the forked thread blocks on the console read. The main thread does not get to execute its signal handler until it grabs the GIL after raw_input returns. (Please correct me if I'm wrong about this -- I'm not an expert on Python's threading implementation.)

Is there a way to read from stdin in a raw_input-like way while allowing the SIGINT to be handled by the main thread and thus bring down the whole process?

[I've observed the behavior above on Mac OS X and a few different Linuxes.]


Edit: I've mischaracterized the underlying problem above. On further investigation, it's the main thread's call to join() that's preventing signal handling: Guido van Rossum himself has explained that the underlying lock acquire in join is uninterruptible. This means that the signal is actually being deferred until the entire thread finishes -- so this really has nothing to do with raw_input at all (just the fact that the background thread is blocking so that the join does not complete).

Civilize answered 13/2, 2012 at 22:59 Comment(1)
One does not simply combine Threads and Interruptions... [boromir.jpg]Czardas
L
6

When join is called with no timeout, it is uninterruptable, but when it is called with a timeout, it is interruptable. Try adding an arbitrary timeout and putting it in a while loop:

while my_thread.isAlive():
    my_thread.join(5.0)
Lipoma answered 20/3, 2012 at 16:29 Comment(1)
Just a small note: the thread needs to be a daemon thread for termination to work with this approach. (Beware when testing this in the interactive interpreter: official main thread is the interpreter itself, and that thread won't exit at the end of your main code, and thus won't terminate even a daemon thread -- unless you do something like sys.exit at the end of your main to force the interpreter to terminate. This does not apply when executed as a script)Without
W
1

There is really no easy way around this, period.

One approach is to reorganize and break up your code in a way that parts of functions which need Ctrl-C interruptibility are executed on the main thread. You use queues to send execution requests and likewise for the result values. You need one input queue for the main thread, and one output queue per non-main thread; and a coordinated main thread exit. Obviously, only one blocking function is executed at any given time this way, which may not be what you want.

Here's a working example of this idea with slightly perverse use of semaphores for the coordinated main thread exit.

Without answered 14/2, 2012 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.