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.
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 promptraw_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 closingsys.stdin
might pull the rug out from underraw_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.