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