Interrupt raw_input in a twisted program

interrupt, keyboard-events, keyboardinterrupt, python, twisted

Solution

The right way to deal with this is to handle console input asynchronously, rather than trying to make a blocking input function interruptable. In other words, `raw_input` is fundamentally the wrong solution to the problem you're attacking.

However, if you really just want to understand what's going on here, the trick is that after calling `reactor.callFromThread(reactor.stop)`, you have to somehow prompt `raw_input` to exit; it's not going to normally. However, since you're running it in a thread, it's not actually interruptable at all, because only the main thread is interruptable in Python. So I think what you want may actually be impossible. I believed that perhaps closing `sys.stdin` might pull the rug out from under `raw_input` even if it were in a thread, but it seems that the underlying libraries are doing something more clever than simply reading from the FD, so closing it does no good.

Problem

I will refer to this explanation and this workaround: So what I am doing is: ``` def interrupted(signum, stackframe): log.warning('interrupted > Got signal: %s', signum) menu.quitMenu = True # to stop my code signal.signal(signal.SIGINT, interrupted) # Handle KeyboardInterrupt ``` The problem is that, while the menu is notified that it must stop, and will do that soon, it can not do it now since it is stucked in a `raw_input`: ``` def askUser(self): current_date = datetime.now().isoformat(' ') choice = raw_input('%s > ' % current_date) return choice ``` So, since twisted is removing the default interrupt handler, `raw_input` is not stopped. I still need to press `enter` after `^C` for it to stop. How can I force the raw_input to be stopped, without installing the default interrupt handler, which is a source of problems in a twisted context (since twisted itself is not expecting to be interrupted) I guess the problem is not related to `raw_input` only: any function taking an unbounded time (or longer than a pre-set limit), should be interrupted somehow. Is there an accepted twisted pattern for this? EDIT This is the full test code: ``` from datetime import datetime class Menu: def __init__(self): self.quitMenu = False def showMenu(self): print ''' A) Do A B) Do B ''' def askUser(self): current_date = datetime.now().isoformat(' ') choice = raw_input('%s > Please select option > ' % current_date) print return choice def stopMe(self): self.quitMenu = True def alive(self): return self.quitMenu == False def doMenuOnce(self): self.showMenu() choice = self.askUser() if not self.alive() : # Maybe somebody has tried to stop the menu while in askUser return if choice == 'A' : print 'A selected' elif choice == 'B' : print 'B selected' else : print 'ERR: choice %s not supported' % (choice) def forever(self): while self.alive(): self.doMenuOnce() from twisted.internet import reactor, threads import signal class MenuTwisted: def __init__(self, menu): self.menu = menu signal.signal(signal.SIGINT, self.interrupted) # Handle KeyboardInterrupt def interrupted(self, signum, stackframe): print 'Interrupted!' self.menu.stopMe() def doMenuOnce(self): threads.deferToThread(self.menu.doMenuOnce).addCallback(self.forever) def forever(self, res=None): if self.menu.alive() : reactor.callLater(0, self.doMenuOnce) else : reactor.callFromThread(reactor.stop) def run(self): self.forever() reactor.run() ``` Which I can run in two different ways. Normal way: ``` menu = Menu() menu.forever() ``` Pressing `^C` stops the program immediately: ``` A) Do A B) Do B 2013-12-03 11:00:26.288846 > Please select option > ^CTraceback (most recent call last): File "twisted_keyboard_interrupt.py", line 72, in <module> menu.forever() File "twisted_keyboard_interrupt.py", line 43, in forever self.doMenuOnce() File "twisted_keyboard_interrupt.py", line 34, in doMenuOnce choice = self.askUser() File "twisted_keyboard_interrupt.py", line 22, in askUser choice = raw_input('%s > Please select option > ' % current_date) KeyboardInterrupt ``` As expected. Twisted way: ``` menu = Menu() menutw = MenuTwisted(menu) menutw.run() ``` Pressing `^C` will produce: ``` A) Do A B) Do B 2013-12-03 11:04:18.678219 > Please select option > ^CInterrupted! ``` But `askUser` is actually not interrupted: I still need to press `enter` for `raw_input` to finish.

Original source

Related problems