##// END OF EJS Templates
- I possibly cross-thread KeyboardInterrupt support for the multithreaded...
fperez -
Show More
@@ -4,7 +4,7 b''
4 All the matplotlib support code was co-developed with John Hunter,
4 All the matplotlib support code was co-developed with John Hunter,
5 matplotlib's author.
5 matplotlib's author.
6
6
7 $Id: Shell.py 2164 2007-03-20 00:15:03Z fperez $"""
7 $Id: Shell.py 2216 2007-04-05 06:00:13Z fperez $"""
8
8
9 #*****************************************************************************
9 #*****************************************************************************
10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
@@ -18,15 +18,26 b" __author__ = '%s <%s>' % Release.authors['Fernando']"
18 __license__ = Release.license
18 __license__ = Release.license
19
19
20 # Code begins
20 # Code begins
21 # Stdlib imports
21 import __builtin__
22 import __builtin__
22 import __main__
23 import __main__
23 import Queue
24 import Queue
25 import inspect
24 import os
26 import os
25 import signal
26 import sys
27 import sys
27 import threading
28 import threading
28 import time
29 import time
29
30
31 from signal import signal, SIGINT
32
33 try:
34 import ctypes
35 HAS_CTYPES = True
36 except ImportError:
37 HAS_CTYPES = False
38
39
40 # IPython imports
30 import IPython
41 import IPython
31 from IPython import ultraTB
42 from IPython import ultraTB
32 from IPython.genutils import Term,warn,error,flag_calls
43 from IPython.genutils import Term,warn,error,flag_calls
@@ -35,12 +46,19 b' from IPython.ipmaker import make_IPython'
35 from IPython.Magic import Magic
46 from IPython.Magic import Magic
36 from IPython.ipstruct import Struct
47 from IPython.ipstruct import Struct
37
48
49 # Globals
38 # global flag to pass around information about Ctrl-C without exceptions
50 # global flag to pass around information about Ctrl-C without exceptions
39 KBINT = False
51 KBINT = False
40
52
41 # global flag to turn on/off Tk support.
53 # global flag to turn on/off Tk support.
42 USE_TK = False
54 USE_TK = False
43
55
56 # ID for the main thread, used for cross-thread exceptions
57 MAIN_THREAD_ID = None
58
59 # Tag when runcode() is active, for exception handling
60 CODE_RUN = None
61
44 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
45 # This class is trivial now, but I want to have it in to publish a clean
63 # This class is trivial now, but I want to have it in to publish a clean
46 # interface. Later when the internals are reorganized, code that uses this
64 # interface. Later when the internals are reorganized, code that uses this
@@ -241,20 +259,70 b' class IPShellEmbed:'
241 self.exit_msg = exit_msg
259 self.exit_msg = exit_msg
242
260
243 #-----------------------------------------------------------------------------
261 #-----------------------------------------------------------------------------
244 def sigint_handler (signum,stack_frame):
262 if HAS_CTYPES:
245 """Sigint handler for threaded apps.
263 # Add async exception support. Trick taken from:
264 # http://sebulba.wikispaces.com/recipe+thread2
265 def _async_raise(tid, exctype):
266 """raises the exception, performs cleanup if needed"""
267 if not inspect.isclass(exctype):
268 raise TypeError("Only types can be raised (not instances)")
269 res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
270 ctypes.py_object(exctype))
271 if res == 0:
272 raise ValueError("invalid thread id")
273 elif res != 1:
274 # """if it returns a number greater than one, you're in trouble,
275 # and you should call it again with exc=NULL to revert the effect"""
276 ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
277 raise SystemError("PyThreadState_SetAsyncExc failed")
278
279 def sigint_handler (signum,stack_frame):
280 """Sigint handler for threaded apps.
281
282 This is a horrible hack to pass information about SIGINT _without_
283 using exceptions, since I haven't been able to properly manage
284 cross-thread exceptions in GTK/WX. In fact, I don't think it can be
285 done (or at least that's my understanding from a c.l.py thread where
286 this was discussed)."""
246
287
247 This is a horrible hack to pass information about SIGINT _without_ using
288 global KBINT
248 exceptions, since I haven't been able to properly manage cross-thread
289
249 exceptions in GTK/WX. In fact, I don't think it can be done (or at least
290 if CODE_RUN:
250 that's my understanding from a c.l.py thread where this was discussed)."""
291 _async_raise(MAIN_THREAD_ID,KeyboardInterrupt)
292 else:
293 KBINT = True
294 print '\nKeyboardInterrupt - Press <Enter> to continue.',
295 Term.cout.flush()
296
297 else:
298 def sigint_handler (signum,stack_frame):
299 """Sigint handler for threaded apps.
300
301 This is a horrible hack to pass information about SIGINT _without_
302 using exceptions, since I haven't been able to properly manage
303 cross-thread exceptions in GTK/WX. In fact, I don't think it can be
304 done (or at least that's my understanding from a c.l.py thread where
305 this was discussed)."""
306
307 global KBINT
308
309 print '\nKeyboardInterrupt - Press <Enter> to continue.',
310 Term.cout.flush()
311 # Set global flag so that runsource can know that Ctrl-C was hit
312 KBINT = True
251
313
252 global KBINT
314
253
315 def _set_main_thread_id():
254 print '\nKeyboardInterrupt - Press <Enter> to continue.',
316 """Ugly hack to find the main thread's ID.
255 Term.cout.flush()
317 """
256 # Set global flag so that runsource can know that Ctrl-C was hit
318 global MAIN_THREAD_ID
257 KBINT = True
319 for tid, tobj in threading._active.items():
320 # There must be a better way to do this than looking at the str() for
321 # each thread object...
322 if 'MainThread' in str(tobj):
323 #print 'main tid:',tid # dbg
324 MAIN_THREAD_ID = tid
325 break
258
326
259 class MTInteractiveShell(InteractiveShell):
327 class MTInteractiveShell(InteractiveShell):
260 """Simple multi-threaded shell."""
328 """Simple multi-threaded shell."""
@@ -338,17 +406,16 b' class MTInteractiveShell(InteractiveShell):'
338
406
339 Multithreaded wrapper around IPython's runcode()."""
407 Multithreaded wrapper around IPython's runcode()."""
340
408
409
410 global CODE_RUN
411
412 # Exceptions need to be raised differently depending on which thread is
413 # active
414 CODE_RUN = True
415
341 # lock thread-protected stuff
416 # lock thread-protected stuff
342 got_lock = self.thread_ready.acquire(False)
417 got_lock = self.thread_ready.acquire(False)
343
418
344 # Install sigint handler
345 try:
346 signal.signal(signal.SIGINT, sigint_handler)
347 except SystemError:
348 # This happens under Windows, which seems to have all sorts
349 # of problems with signal handling. Oh well...
350 pass
351
352 if self._kill:
419 if self._kill:
353 print >>Term.cout, 'Closing threads...',
420 print >>Term.cout, 'Closing threads...',
354 Term.cout.flush()
421 Term.cout.flush()
@@ -356,6 +423,15 b' class MTInteractiveShell(InteractiveShell):'
356 tokill()
423 tokill()
357 print >>Term.cout, 'Done.'
424 print >>Term.cout, 'Done.'
358
425
426 # Install sigint handler. It feels stupid to do this on every single
427 # pass
428 try:
429 signal(SIGINT,sigint_handler)
430 except SystemError:
431 # This happens under Windows, which seems to have all sorts
432 # of problems with signal handling. Oh well...
433 pass
434
359 # Flush queue of pending code by calling the run methood of the parent
435 # Flush queue of pending code by calling the run methood of the parent
360 # class with all items which may be in the queue.
436 # class with all items which may be in the queue.
361 while 1:
437 while 1:
@@ -372,6 +448,9 b' class MTInteractiveShell(InteractiveShell):'
372 # We're done with thread-protected variables
448 # We're done with thread-protected variables
373 if got_lock:
449 if got_lock:
374 self.thread_ready.release()
450 self.thread_ready.release()
451
452 # We're done...
453 CODE_RUN = False
375 # This MUST return true for gtk threading to work
454 # This MUST return true for gtk threading to work
376 return True
455 return True
377
456
@@ -597,7 +676,16 b' def hijack_gtk():'
597 # desired, the factory function start() below should be used instead (it
676 # desired, the factory function start() below should be used instead (it
598 # selects the proper threaded class).
677 # selects the proper threaded class).
599
678
600 class IPShellGTK(threading.Thread):
679 class IPThread(threading.Thread):
680 def run(self):
681 self.IP.mainloop(self._banner)
682 self.IP.kill()
683
684 def start(self):
685 threading.Thread.start(self)
686 _set_main_thread_id()
687
688 class IPShellGTK(IPThread):
601 """Run a gtk mainloop() in a separate thread.
689 """Run a gtk mainloop() in a separate thread.
602
690
603 Python commands can be passed to the thread where they will be executed.
691 Python commands can be passed to the thread where they will be executed.
@@ -633,10 +721,6 b' class IPShellGTK(threading.Thread):'
633
721
634 threading.Thread.__init__(self)
722 threading.Thread.__init__(self)
635
723
636 def run(self):
637 self.IP.mainloop(self._banner)
638 self.IP.kill()
639
640 def mainloop(self,sys_exit=0,banner=None):
724 def mainloop(self,sys_exit=0,banner=None):
641
725
642 self._banner = banner
726 self._banner = banner
@@ -680,7 +764,8 b' class IPShellGTK(threading.Thread):'
680 time.sleep(0.01)
764 time.sleep(0.01)
681 return True
765 return True
682
766
683 class IPShellWX(threading.Thread):
767
768 class IPShellWX(IPThread):
684 """Run a wx mainloop() in a separate thread.
769 """Run a wx mainloop() in a separate thread.
685
770
686 Python commands can be passed to the thread where they will be executed.
771 Python commands can be passed to the thread where they will be executed.
@@ -721,7 +806,6 b' class IPShellWX(threading.Thread):'
721 # Allows us to use both Tk and GTK.
806 # Allows us to use both Tk and GTK.
722 self.tk = get_tk()
807 self.tk = get_tk()
723
808
724
725 # HACK: slot for banner in self; it will be passed to the mainloop
809 # HACK: slot for banner in self; it will be passed to the mainloop
726 # method only and .run() needs it. The actual value will be set by
810 # method only and .run() needs it. The actual value will be set by
727 # .mainloop().
811 # .mainloop().
@@ -734,10 +818,6 b' class IPShellWX(threading.Thread):'
734 self.app.agent.timer.Stop()
818 self.app.agent.timer.Stop()
735 self.app.ExitMainLoop()
819 self.app.ExitMainLoop()
736
820
737 def run(self):
738 self.IP.mainloop(self._banner)
739 self.IP.kill()
740
741 def mainloop(self,sys_exit=0,banner=None):
821 def mainloop(self,sys_exit=0,banner=None):
742
822
743 self._banner = banner
823 self._banner = banner
@@ -774,13 +854,14 b' class IPShellWX(threading.Thread):'
774 self.agent.Show(False)
854 self.agent.Show(False)
775 self.agent.StartWork()
855 self.agent.StartWork()
776 return True
856 return True
777
857
858 _set_main_thread_id()
778 self.app = App(redirect=False)
859 self.app = App(redirect=False)
779 self.wx_mainloop(self.app)
860 self.wx_mainloop(self.app)
780 self.join()
861 self.join()
781
862
782
863
783 class IPShellQt(threading.Thread):
864 class IPShellQt(IPThread):
784 """Run a Qt event loop in a separate thread.
865 """Run a Qt event loop in a separate thread.
785
866
786 Python commands can be passed to the thread where they will be executed.
867 Python commands can be passed to the thread where they will be executed.
@@ -825,10 +906,6 b' class IPShellQt(threading.Thread):'
825
906
826 threading.Thread.__init__(self)
907 threading.Thread.__init__(self)
827
908
828 def run(self):
829 self.IP.mainloop(self._banner)
830 self.IP.kill()
831
832 def mainloop(self,sys_exit=0,banner=None):
909 def mainloop(self,sys_exit=0,banner=None):
833
910
834 import qt
911 import qt
@@ -854,7 +931,7 b' class IPShellQt(threading.Thread):'
854 return result
931 return result
855
932
856
933
857 class IPShellQt4(threading.Thread):
934 class IPShellQt4(IPThread):
858 """Run a Qt event loop in a separate thread.
935 """Run a Qt event loop in a separate thread.
859
936
860 Python commands can be passed to the thread where they will be executed.
937 Python commands can be passed to the thread where they will be executed.
@@ -899,10 +976,6 b' class IPShellQt4(threading.Thread):'
899
976
900 threading.Thread.__init__(self)
977 threading.Thread.__init__(self)
901
978
902 def run(self):
903 self.IP.mainloop(self._banner)
904 self.IP.kill()
905
906 def mainloop(self,sys_exit=0,banner=None):
979 def mainloop(self,sys_exit=0,banner=None):
907
980
908 from PyQt4 import QtCore, QtGui
981 from PyQt4 import QtCore, QtGui
@@ -1,3 +1,24 b''
1 2007-04-04 Fernando Perez <Fernando.Perez@colorado.edu>
2
3 * IPython/Shell.py (sigint_handler): I *THINK* I finally got
4 asynchronous exceptions working, i.e., Ctrl-C can actually
5 interrupt long-running code in the multithreaded shells.
6
7 This is using Tomer Filiba's great ctypes-based trick:
8 http://sebulba.wikispaces.com/recipe+thread2. I'd already tried
9 this in the past, but hadn't been able to make it work before. So
10 far it looks like it's actually running, but this needs more
11 testing. If it really works, I'll be *very* happy, and we'll owe
12 a huge thank you to Tomer. My current implementation is ugly,
13 hackish and uses nasty globals, but I don't want to try and clean
14 anything up until we know if it actually works.
15
16 NOTE: this feature needs ctypes to work. ctypes is included in
17 Python2.5, but 2.4 users will need to manually install it. This
18 feature makes multi-threaded shells so much more usable that it's
19 a minor price to pay (ctypes is very easy to install, already a
20 requirement for win32 and available in major linux distros).
21
1 2007-04-04 Ville Vainio <vivainio@gmail.com>
22 2007-04-04 Ville Vainio <vivainio@gmail.com>
2
23
3 * Extensions/ipy_completers.py, ipy_stock_completers.py:
24 * Extensions/ipy_completers.py, ipy_stock_completers.py:
General Comments 0
You need to be logged in to leave comments. Login now