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