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