##// END OF EJS Templates
- I possibly cross-thread KeyboardInterrupt support for the multithreaded...
fperez -
Show More
@@ -4,7 +4,7 b''
4 4 All the matplotlib support code was co-developed with John Hunter,
5 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 10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
@@ -18,15 +18,26 b" __author__ = '%s <%s>' % Release.authors['Fernando']"
18 18 __license__ = Release.license
19 19
20 20 # Code begins
21 # Stdlib imports
21 22 import __builtin__
22 23 import __main__
23 24 import Queue
25 import inspect
24 26 import os
25 import signal
26 27 import sys
27 28 import threading
28 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 41 import IPython
31 42 from IPython import ultraTB
32 43 from IPython.genutils import Term,warn,error,flag_calls
@@ -35,12 +46,19 b' from IPython.ipmaker import make_IPython'
35 46 from IPython.Magic import Magic
36 47 from IPython.ipstruct import Struct
37 48
49 # Globals
38 50 # global flag to pass around information about Ctrl-C without exceptions
39 51 KBINT = False
40 52
41 53 # global flag to turn on/off Tk support.
42 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 63 # This class is trivial now, but I want to have it in to publish a clean
46 64 # interface. Later when the internals are reorganized, code that uses this
@@ -241,20 +259,70 b' class IPShellEmbed:'
241 259 self.exit_msg = exit_msg
242 260
243 261 #-----------------------------------------------------------------------------
244 def sigint_handler (signum,stack_frame):
245 """Sigint handler for threaded apps.
262 if HAS_CTYPES:
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
248 exceptions, since I haven't been able to properly manage cross-thread
249 exceptions in GTK/WX. In fact, I don't think it can be done (or at least
250 that's my understanding from a c.l.py thread where this was discussed)."""
288 global KBINT
289
290 if CODE_RUN:
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
253
254 print '\nKeyboardInterrupt - Press <Enter> to continue.',
255 Term.cout.flush()
256 # Set global flag so that runsource can know that Ctrl-C was hit
257 KBINT = True
314
315 def _set_main_thread_id():
316 """Ugly hack to find the main thread's ID.
317 """
318 global MAIN_THREAD_ID
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 327 class MTInteractiveShell(InteractiveShell):
260 328 """Simple multi-threaded shell."""
@@ -338,17 +406,16 b' class MTInteractiveShell(InteractiveShell):'
338 406
339 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 416 # lock thread-protected stuff
342 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 419 if self._kill:
353 420 print >>Term.cout, 'Closing threads...',
354 421 Term.cout.flush()
@@ -356,6 +423,15 b' class MTInteractiveShell(InteractiveShell):'
356 423 tokill()
357 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 435 # Flush queue of pending code by calling the run methood of the parent
360 436 # class with all items which may be in the queue.
361 437 while 1:
@@ -372,6 +448,9 b' class MTInteractiveShell(InteractiveShell):'
372 448 # We're done with thread-protected variables
373 449 if got_lock:
374 450 self.thread_ready.release()
451
452 # We're done...
453 CODE_RUN = False
375 454 # This MUST return true for gtk threading to work
376 455 return True
377 456
@@ -597,7 +676,16 b' def hijack_gtk():'
597 676 # desired, the factory function start() below should be used instead (it
598 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 689 """Run a gtk mainloop() in a separate thread.
602 690
603 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 722 threading.Thread.__init__(self)
635 723
636 def run(self):
637 self.IP.mainloop(self._banner)
638 self.IP.kill()
639
640 724 def mainloop(self,sys_exit=0,banner=None):
641 725
642 726 self._banner = banner
@@ -680,7 +764,8 b' class IPShellGTK(threading.Thread):'
680 764 time.sleep(0.01)
681 765 return True
682 766
683 class IPShellWX(threading.Thread):
767
768 class IPShellWX(IPThread):
684 769 """Run a wx mainloop() in a separate thread.
685 770
686 771 Python commands can be passed to the thread where they will be executed.
@@ -721,7 +806,6 b' class IPShellWX(threading.Thread):'
721 806 # Allows us to use both Tk and GTK.
722 807 self.tk = get_tk()
723 808
724
725 809 # HACK: slot for banner in self; it will be passed to the mainloop
726 810 # method only and .run() needs it. The actual value will be set by
727 811 # .mainloop().
@@ -734,10 +818,6 b' class IPShellWX(threading.Thread):'
734 818 self.app.agent.timer.Stop()
735 819 self.app.ExitMainLoop()
736 820
737 def run(self):
738 self.IP.mainloop(self._banner)
739 self.IP.kill()
740
741 821 def mainloop(self,sys_exit=0,banner=None):
742 822
743 823 self._banner = banner
@@ -774,13 +854,14 b' class IPShellWX(threading.Thread):'
774 854 self.agent.Show(False)
775 855 self.agent.StartWork()
776 856 return True
777
857
858 _set_main_thread_id()
778 859 self.app = App(redirect=False)
779 860 self.wx_mainloop(self.app)
780 861 self.join()
781 862
782 863
783 class IPShellQt(threading.Thread):
864 class IPShellQt(IPThread):
784 865 """Run a Qt event loop in a separate thread.
785 866
786 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 907 threading.Thread.__init__(self)
827 908
828 def run(self):
829 self.IP.mainloop(self._banner)
830 self.IP.kill()
831
832 909 def mainloop(self,sys_exit=0,banner=None):
833 910
834 911 import qt
@@ -854,7 +931,7 b' class IPShellQt(threading.Thread):'
854 931 return result
855 932
856 933
857 class IPShellQt4(threading.Thread):
934 class IPShellQt4(IPThread):
858 935 """Run a Qt event loop in a separate thread.
859 936
860 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 977 threading.Thread.__init__(self)
901 978
902 def run(self):
903 self.IP.mainloop(self._banner)
904 self.IP.kill()
905
906 979 def mainloop(self,sys_exit=0,banner=None):
907 980
908 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 22 2007-04-04 Ville Vainio <vivainio@gmail.com>
2 23
3 24 * Extensions/ipy_completers.py, ipy_stock_completers.py:
General Comments 0
You need to be logged in to leave comments. Login now