##// END OF EJS Templates
first version of IPython.twshell (Twisted reactor based interactive shell)
Ville M. Vainio -
Show More
@@ -0,0 +1,283 b''
1 import sys
2
3 from twisted.internet import gtk2reactor
4 gtk2reactor.install()
5
6 from twisted.internet import reactor, threads
7
8 """ change here to choose the plot shell with the MT option
9 which should cost extra """
10
11 from IPython.ipmaker import make_IPython
12 from IPython.iplib import InteractiveShell
13 from IPython.ipstruct import Struct
14 import Queue,thread,threading,signal
15
16 from IPython.Shell import *
17
18 from signal import signal, SIGINT
19
20 def hijack_reactor():
21 """Modifies Twisted's reactor with a dummy so user code does
22 not block IPython. This function returns the original
23 'twisted.internet.reactor' that has been hijacked.
24
25 NOTE: Make sure you call this *AFTER* you've installed
26 the reactor of your choice.
27 """
28 from twisted import internet
29 orig_reactor = internet.reactor
30
31 class DummyReactor(object):
32 def run(self):
33 pass
34 def __getattr__(self, name):
35 return getattr(orig_reactor, name)
36 def __setattr__(self, name, value):
37 return setattr(orig_reactor, name, value)
38
39 internet.reactor = DummyReactor()
40 return orig_reactor
41
42 class TwistedInteractiveShell(InteractiveShell):
43 """Simple multi-threaded shell."""
44
45 # Threading strategy taken from:
46 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian
47 # McErlean and John Finlay. Modified with corrections by Antoon Pardon,
48 # from the pygtk mailing list, to avoid lockups with system calls.
49
50 # class attribute to indicate whether the class supports threads or not.
51 # Subclasses with thread support should override this as needed.
52 isthreaded = True
53
54 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
55 user_ns=None,user_global_ns=None,banner2='',**kw):
56 """Similar to the normal InteractiveShell, but with threading control"""
57
58 InteractiveShell.__init__(self,name,usage,rc,user_ns,
59 user_global_ns,banner2)
60
61
62 # A queue to hold the code to be executed.
63 self.code_queue = Queue.Queue()
64
65 # Stuff to do at closing time
66 self._kill = None
67 on_kill = kw.get('on_kill', [])
68 # Check that all things to kill are callable:
69 for t in on_kill:
70 if not callable(t):
71 raise TypeError,'on_kill must be a list of callables'
72 self.on_kill = on_kill
73 # thread identity of the "worker thread" (that may execute code directly)
74 self.worker_ident = None
75 self.reactor_started = False
76 self.first_run = True
77
78 def runsource(self, source, filename="<input>", symbol="single"):
79 """Compile and run some source in the interpreter.
80
81 Modified version of code.py's runsource(), to handle threading issues.
82 See the original for full docstring details."""
83
84 print "rs"
85 global KBINT
86
87 # If Ctrl-C was typed, we reset the flag and return right away
88 if KBINT:
89 KBINT = False
90 return False
91
92 if self._kill:
93 # can't queue new code if we are being killed
94 return True
95
96 try:
97 code = self.compile(source, filename, symbol)
98 except (OverflowError, SyntaxError, ValueError):
99 # Case 1
100 self.showsyntaxerror(filename)
101 return False
102
103 if code is None:
104 # Case 2
105 return True
106
107 # shortcut - if we are in worker thread, or the worker thread is not running,
108 # execute directly (to allow recursion and prevent deadlock if code is run early
109 # in IPython construction)
110
111 if (not self.reactor_started or (self.worker_ident is None and not self.first_run)
112 or self.worker_ident == thread.get_ident()):
113 InteractiveShell.runcode(self,code)
114 return
115
116 self.first_run = False
117 # Case 3
118 # Store code in queue, so the execution thread can handle it.
119
120 self.first_run = False
121 completed_ev, received_ev = threading.Event(), threading.Event()
122
123 self.code_queue.put((code,completed_ev, received_ev))
124
125 reactor.callLater(0.0,self.runcode)
126 received_ev.wait(5)
127 if not received_ev.isSet():
128 # the mainloop is dead, start executing code directly
129 print "Warning: Timeout for mainloop thread exceeded"
130 print "switching to nonthreaded mode (until mainloop wakes up again)"
131 self.worker_ident = None
132 else:
133 completed_ev.wait()
134
135 return False
136
137 def runcode(self):
138 """Execute a code object.
139
140 Multithreaded wrapper around IPython's runcode()."""
141
142 global CODE_RUN
143
144 # we are in worker thread, stash out the id for runsource()
145 self.worker_ident = thread.get_ident()
146
147 if self._kill:
148 print >>Term.cout, 'Closing threads...',
149 Term.cout.flush()
150 for tokill in self.on_kill:
151 tokill()
152 print >>Term.cout, 'Done.'
153 # allow kill() to return
154 self._kill.set()
155 return True
156
157 # Install sigint handler. We do it every time to ensure that if user
158 # code modifies it, we restore our own handling.
159 try:
160 signal(SIGINT,sigint_handler)
161 except SystemError:
162 # This happens under Windows, which seems to have all sorts
163 # of problems with signal handling. Oh well...
164 pass
165
166 # Flush queue of pending code by calling the run methood of the parent
167 # class with all items which may be in the queue.
168 code_to_run = None
169 while 1:
170 try:
171 code_to_run, completed_ev, received_ev = self.code_queue.get_nowait()
172 except Queue.Empty:
173 break
174 received_ev.set()
175
176
177 # Exceptions need to be raised differently depending on which
178 # thread is active. This convoluted try/except is only there to
179 # protect against asynchronous exceptions, to ensure that a KBINT
180 # at the wrong time doesn't deadlock everything. The global
181 # CODE_TO_RUN is set to true/false as close as possible to the
182 # runcode() call, so that the KBINT handler is correctly informed.
183 try:
184 try:
185 CODE_RUN = True
186 InteractiveShell.runcode(self,code_to_run)
187 except KeyboardInterrupt:
188 print "Keyboard interrupted in mainloop"
189 while not self.code_queue.empty():
190 code = self.code_queue.get_nowait()
191 break
192 finally:
193 CODE_RUN = False
194 # allow runsource() return from wait
195 completed_ev.set()
196
197 # This MUST return true for gtk threading to work
198 return True
199
200 def kill(self):
201 """Kill the thread, returning when it has been shut down."""
202 self._kill = threading.Event()
203 reactor.callLater(0.0,self.runcode)
204 self._kill.wait()
205
206
207
208 class IPShellTwisted():
209 """Run a Twisted reactor while in an IPython session.
210
211 Python commands can be passed to the thread where they will be
212 executed. This is implemented by periodically checking for
213 passed code using a Twisted reactor callback.
214 """
215
216 TIMEOUT = 0.01 # Millisecond interval between reactor runs.
217
218 def __init__(self, argv=None, user_ns=None, debug=1,
219 shell_class=TwistedInteractiveShell):
220
221 from twisted.internet import reactor
222 self.reactor = hijack_reactor()
223
224 mainquit = self.reactor.stop
225
226 # Make sure IPython keeps going after reactor stop.
227 def reactorstop():
228 pass
229 self.reactor.stop = reactorstop
230 reactorrun_orig = self.reactor.run
231 self.quitting = False
232 def reactorrun():
233 while True and not self.quitting:
234 reactorrun_orig()
235 self.reactor.run = reactorrun
236
237 self.IP = make_IPython(argv, user_ns=user_ns, debug=debug,
238 shell_class=shell_class,
239 on_kill=[mainquit])
240
241 # threading.Thread.__init__(self)
242
243 def run(self):
244 self.IP.mainloop()
245 self.quitting = True
246 self.IP.kill()
247
248 def mainloop(self):
249 def mainLoopThreadDeath(r):
250 print "mainLoopThreadDeath: ", str(r)
251 def spawnMainloopThread():
252 d=threads.deferToThread(self.run)
253 d.addBoth(mainLoopThreadDeath)
254 reactor.callWhenRunning(spawnMainloopThread)
255 self.IP.reactor_started = True
256 self.reactor.run()
257 print "mainloop ending...."
258
259 exists = True
260
261
262 if __name__ == '__main__':
263 # Sample usage.
264
265 # Create the shell object. This steals twisted.internet.reactor
266 # for its own purposes, to make sure you've already installed a
267 # reactor of your choice.
268 shell = IPShellTwisted(
269 argv=[],
270 user_ns={'__name__': '__example__',
271 'hello': 'world',
272 },
273 )
274
275 # Run the mainloop. This runs the actual reactor.run() method.
276 # The twisted.internet.reactor object at this point is a dummy
277 # object that passes through to the actual reactor, but prevents
278 # run() from being called on it again.
279 shell.mainloop()
280
281 # You must exit IPython to terminate your program.
282 print 'Goodbye!'
283
General Comments 0
You need to be logged in to leave comments. Login now