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