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