diff --git a/IPython/Extensions/ipy_bzr.py b/IPython/Extensions/ipy_bzr.py index bba6477..873a84f 100644 --- a/IPython/Extensions/ipy_bzr.py +++ b/IPython/Extensions/ipy_bzr.py @@ -8,7 +8,7 @@ are at the bottom of the file, the rest is left untouched. Must be loaded with ip.load('ipy_bzr') """ -a + # Copyright (C) 2004, 2005 Aaron Bentley # # diff --git a/IPython/Shell.py b/IPython/Shell.py index 74a5bb0..910ea70 100644 --- a/IPython/Shell.py +++ b/IPython/Shell.py @@ -358,18 +358,13 @@ class MTInteractiveShell(InteractiveShell): InteractiveShell.__init__(self,name,usage,rc,user_ns, user_global_ns,banner2) - # Locking control variable. - self.thread_ready = threading.Condition(threading.RLock()) - # A queue to hold the code to be executed. A scalar variable is NOT - # enough, because uses like macros cause reentrancy. + # A queue to hold the code to be executed. self.code_queue = Queue.Queue() # Stuff to do at closing time - self._kill = False - on_kill = kw.get('on_kill') - if on_kill is None: - on_kill = [] + self._kill = None + on_kill = kw.get('on_kill', []) # Check that all things to kill are callable: for t in on_kill: if not callable(t): @@ -377,18 +372,23 @@ class MTInteractiveShell(InteractiveShell): self.on_kill = on_kill # thread identity of the "worker thread" (that may execute code directly) self.worker_ident = None + def runsource(self, source, filename="", symbol="single"): """Compile and run some source in the interpreter. Modified version of code.py's runsource(), to handle threading issues. See the original for full docstring details.""" - + global KBINT # If Ctrl-C was typed, we reset the flag and return right away if KBINT: KBINT = False return False + + if self._kill: + # can't queue new code if we are being killed + return True try: code = self.compile(source, filename, symbol) @@ -401,38 +401,40 @@ class MTInteractiveShell(InteractiveShell): # Case 2 return True - # Case 3 - # Store code in queue, so the execution thread can handle it. - - # Note that with macros and other applications, we MAY re-enter this - # section, so we have to acquire the lock with non-blocking semantics, - # else we deadlock. - # shortcut - if we are in worker thread, or the worker thread is not running, # execute directly (to allow recursion and prevent deadlock if code is run early # in IPython construction) - if self.worker_ident is None or self.worker_ident == thread.get_ident(): + if (self.worker_ident is None or self.worker_ident == thread.get_ident()): InteractiveShell.runcode(self,code) return - - got_lock = self.thread_ready.acquire(blocking=False) - self.code_queue.put(code) - if got_lock: - self.thread_ready.wait() # Wait until processed in timeout interval - self.thread_ready.release() + # Case 3 + # Store code in queue, so the execution thread can handle it. + + completed_ev, received_ev = threading.Event(), threading.Event() + + self.code_queue.put((code,completed_ev, received_ev)) + # first make sure the message was received, with timeout + received_ev.wait(5) + if not received_ev.isSet(): + # the mainloop is dead, start executing code directly + print "Warning: Timeout for mainloop thread exceeded" + print "switching to nonthreaded mode (until mainloop wakes up again)" + self.worker_ident = None + else: + completed_ev.wait() return False def runcode(self): """Execute a code object. Multithreaded wrapper around IPython's runcode().""" - + global CODE_RUN - # lock thread-protected stuff + + # we are in worker thread, stash out the id for runsource() self.worker_ident = thread.get_ident() - got_lock = self.thread_ready.acquire() if self._kill: print >>Term.cout, 'Closing threads...', @@ -440,6 +442,9 @@ class MTInteractiveShell(InteractiveShell): for tokill in self.on_kill: tokill() print >>Term.cout, 'Done.' + # allow kill() to return + self._kill.set() + return True # Install sigint handler. We do it every time to ensure that if user # code modifies it, we restore our own handling. @@ -455,9 +460,11 @@ class MTInteractiveShell(InteractiveShell): code_to_run = None while 1: try: - code_to_run = self.code_queue.get_nowait() + code_to_run, completed_ev, received_ev = self.code_queue.get_nowait() except Queue.Empty: break + received_ev.set() + # Exceptions need to be raised differently depending on which # thread is active. This convoluted try/except is only there to # protect against asynchronous exceptions, to ensure that a KBINT @@ -471,28 +478,23 @@ class MTInteractiveShell(InteractiveShell): except KeyboardInterrupt: print "Keyboard interrupted in mainloop" while not self.code_queue.empty(): - self.code_queue.get_nowait() + code, ev1,ev2 = self.code_queue.get_nowait() + ev1.set() + ev2.set() break finally: - if got_lock: - CODE_RUN = False - - # We're done with thread-protected variables - if code_to_run is not None: - self.thread_ready.notify() - self.thread_ready.release() - - # We're done... - CODE_RUN = False + CODE_RUN = False + # allow runsource() return from wait + completed_ev.set() + + # This MUST return true for gtk threading to work return True def kill(self): """Kill the thread, returning when it has been shut down.""" - got_lock = self.thread_ready.acquire(False) - self._kill = True - if got_lock: - self.thread_ready.release() + self._kill = threading.Event() + self._kill.wait() class MatplotlibShellBase: """Mixin class to provide the necessary modifications to regular IPython @@ -1153,10 +1155,13 @@ def _select_shell(argv): } all_opts = set(['tk','pylab','gthread','qthread','q4thread','wthread', - 'tkthread']) + 'tkthread', 'twisted']) user_opts = set([s.replace('-','') for s in argv[:3]]) special_opts = user_opts & all_opts + if 'twisted' in special_opts: + import twshell + return twshell.IPShellTwisted if 'tk' in special_opts: USE_TK = True special_opts.remove('tk') diff --git a/IPython/ipmaker.py b/IPython/ipmaker.py index 14c084a..8b37dbd 100644 --- a/IPython/ipmaker.py +++ b/IPython/ipmaker.py @@ -184,10 +184,9 @@ object? -> Details about 'object'. ?object also works, ?? prints more. # Options that can *only* appear at the cmd line (not in rcfiles). - # The "ignore" option is a kludge so that Emacs buffers don't crash, since - # the 'C-c !' command in emacs automatically appends a -i option at the end. cmdline_only = ('help interact|i ipythondir=s Version upgrade ' - 'gthread! qthread! q4thread! wthread! tkthread! pylab! tk!') + 'gthread! qthread! q4thread! wthread! tkthread! pylab! tk! ' + 'twisted!') # Build the actual name list to be used by DPyGetOpt opts_names = qw(cmdline_opts) + qw(cmdline_only) @@ -245,6 +244,7 @@ object? -> Details about 'object'. ?object also works, ?? prints more. system_verbose = 0, term_title = 1, tk = 0, + twisted= 0, upgrade = 0, Version = 0, wildcards_case_sensitive = 1, diff --git a/IPython/shellglobals.py b/IPython/shellglobals.py index 7b69bec..48994bf 100644 --- a/IPython/shellglobals.py +++ b/IPython/shellglobals.py @@ -80,3 +80,17 @@ else: Term.cout.flush() # Set global flag so that runsource can know that Ctrl-C was hit KBINT = True + +def run_in_frontend(src): + """ Check if source snippet can be run in the REPL thread, as opposed to GUI mainloop + + (to prevent unnecessary hanging of mainloop). + + """ + + if src.startswith('_ip.system(') and not '\n' in src: + return True + return False + + + diff --git a/IPython/twshell.py b/IPython/twshell.py index ad30ba2..d0c2696 100644 --- a/IPython/twshell.py +++ b/IPython/twshell.py @@ -1,13 +1,7 @@ import sys -from twisted.internet import gtk2reactor -gtk2reactor.install() - from twisted.internet import reactor, threads -""" change here to choose the plot shell with the MT option - which should cost extra """ - from IPython.ipmaker import make_IPython from IPython.iplib import InteractiveShell from IPython.ipstruct import Struct @@ -16,6 +10,11 @@ from signal import signal, SIGINT from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no import shellglobals +def install_gtk2(): + """ Install gtk2 reactor, needs to be called bef """ + from twisted.internet import gtk2reactor + gtk2reactor.install() + def hijack_reactor(): """Modifies Twisted's reactor with a dummy so user code does @@ -106,7 +105,7 @@ class TwistedInteractiveShell(InteractiveShell): # in IPython construction) if (not self.reactor_started or (self.worker_ident is None and not self.first_run) - or self.worker_ident == thread.get_ident()): + or self.worker_ident == thread.get_ident() or shellglobals.run_in_frontend(source)): InteractiveShell.runcode(self,code) return @@ -125,8 +124,7 @@ class TwistedInteractiveShell(InteractiveShell): print "Warning: Timeout for mainloop thread exceeded" print "switching to nonthreaded mode (until mainloop wakes up again)" self.worker_ident = None - else: - shellglobals.CURRENT_COMPLETE_EV = completed_ev + else: completed_ev.wait() return False diff --git a/doc/ipython.1 b/doc/ipython.1 index be9ce42..5b11cf7 100644 --- a/doc/ipython.1 +++ b/doc/ipython.1 @@ -36,11 +36,11 @@ The following special options are ONLY valid at the beginning of the command line, and not later. This is because they control the initialization of ipython itself, before the normal option-handling mechanism is active. .TP -.B \-gthread, \-qthread, \-q4thread, \-wthread, \-pylab +.B \-gthread, \-qthread, \-q4thread, \-wthread, \-pylab, \-twisted Only ONE of these can be given, and it can only be given as the first option passed to IPython (it will have no effect in any other position). They provide -threading support for the GTK, QT3, QT4 and WXWidgets toolkits, and for the -matplotlib library. +threading support for the GTK, QT3, QT4 and WXWidgets toolkits, for the +matplotlib library and Twisted reactor. .br .sp 1 With any of the first four options, IPython starts running a separate thread @@ -56,6 +56,10 @@ request a specific version of wx to be used. This requires that you have the distributions. .br .sp 1 +If \-twisted is given, IPython start a Twisted reactor and runs IPython mainloop +in a dedicated thread, passing commands to be run inside the Twisted reactor. +.br +.sp 1 If \-pylab is given, IPython loads special support for the matplotlib library (http://matplotlib.sourceforge.net), allowing interactive usage of any of its backends as defined in the user's .matplotlibrc file. It automatically