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 216 |
|
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,13 +259,50 b' class IPShellEmbed:' | |||||
241 | self.exit_msg = exit_msg |
|
259 | self.exit_msg = exit_msg | |
242 |
|
260 | |||
243 | #----------------------------------------------------------------------------- |
|
261 | #----------------------------------------------------------------------------- | |
|
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).""" | |||
|
287 | ||||
|
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: | |||
244 | def sigint_handler (signum,stack_frame): |
|
298 | def sigint_handler (signum,stack_frame): | |
245 | """Sigint handler for threaded apps. |
|
299 | """Sigint handler for threaded apps. | |
246 |
|
300 | |||
247 |
This is a horrible hack to pass information about SIGINT _without_ |
|
301 | This is a horrible hack to pass information about SIGINT _without_ | |
248 |
exceptions, since I haven't been able to properly manage |
|
302 | using exceptions, since I haven't been able to properly manage | |
249 |
exceptions in GTK/WX. In fact, I don't think it can be |
|
303 | cross-thread exceptions in GTK/WX. In fact, I don't think it can be | |
250 |
that's my understanding from a c.l.py thread where |
|
304 | done (or at least that's my understanding from a c.l.py thread where | |
|
305 | this was discussed).""" | |||
251 |
|
306 | |||
252 | global KBINT |
|
307 | global KBINT | |
253 |
|
308 | |||
@@ -256,6 +311,19 b' def sigint_handler (signum,stack_frame):' | |||||
256 | # Set global flag so that runsource can know that Ctrl-C was hit |
|
311 | # Set global flag so that runsource can know that Ctrl-C was hit | |
257 | KBINT = True |
|
312 | KBINT = True | |
258 |
|
313 | |||
|
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 | |||
|
326 | ||||
259 | class MTInteractiveShell(InteractiveShell): |
|
327 | class MTInteractiveShell(InteractiveShell): | |
260 | """Simple multi-threaded shell.""" |
|
328 | """Simple multi-threaded shell.""" | |
261 |
|
329 | |||
@@ -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 IP |
|
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 | |
@@ -775,12 +855,13 b' class IPShellWX(threading.Thread):' | |||||
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( |
|
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( |
|
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