|
1 import sys |
|
2 import linecache |
|
3 import time |
|
4 import socket |
|
5 import traceback |
|
6 import thread |
|
7 import threading |
|
8 import Queue |
|
9 |
|
10 import CallTips |
|
11 import AutoComplete |
|
12 |
|
13 import RemoteDebugger |
|
14 import RemoteObjectBrowser |
|
15 import StackViewer |
|
16 import rpc |
|
17 |
|
18 import __main__ |
|
19 |
|
20 LOCALHOST = '127.0.0.1' |
|
21 |
|
22 try: |
|
23 import warnings |
|
24 except ImportError: |
|
25 pass |
|
26 else: |
|
27 def idle_formatwarning_subproc(message, category, filename, lineno, |
|
28 file=None, line=None): |
|
29 """Format warnings the IDLE way""" |
|
30 s = "\nWarning (from warnings module):\n" |
|
31 s += ' File \"%s\", line %s\n' % (filename, lineno) |
|
32 line = linecache.getline(filename, lineno).strip() \ |
|
33 if line is None else line |
|
34 if line: |
|
35 s += " %s\n" % line |
|
36 s += "%s: %s\n" % (category.__name__, message) |
|
37 return s |
|
38 warnings.formatwarning = idle_formatwarning_subproc |
|
39 |
|
40 # Thread shared globals: Establish a queue between a subthread (which handles |
|
41 # the socket) and the main thread (which runs user code), plus global |
|
42 # completion, exit and interruptable (the main thread) flags: |
|
43 |
|
44 exit_now = False |
|
45 quitting = False |
|
46 interruptable = False |
|
47 |
|
48 def main(del_exitfunc=False): |
|
49 """Start the Python execution server in a subprocess |
|
50 |
|
51 In the Python subprocess, RPCServer is instantiated with handlerclass |
|
52 MyHandler, which inherits register/unregister methods from RPCHandler via |
|
53 the mix-in class SocketIO. |
|
54 |
|
55 When the RPCServer 'server' is instantiated, the TCPServer initialization |
|
56 creates an instance of run.MyHandler and calls its handle() method. |
|
57 handle() instantiates a run.Executive object, passing it a reference to the |
|
58 MyHandler object. That reference is saved as attribute rpchandler of the |
|
59 Executive instance. The Executive methods have access to the reference and |
|
60 can pass it on to entities that they command |
|
61 (e.g. RemoteDebugger.Debugger.start_debugger()). The latter, in turn, can |
|
62 call MyHandler(SocketIO) register/unregister methods via the reference to |
|
63 register and unregister themselves. |
|
64 |
|
65 """ |
|
66 global exit_now |
|
67 global quitting |
|
68 global no_exitfunc |
|
69 no_exitfunc = del_exitfunc |
|
70 port = 8833 |
|
71 #time.sleep(15) # test subprocess not responding |
|
72 if sys.argv[1:]: |
|
73 port = int(sys.argv[1]) |
|
74 sys.argv[:] = [""] |
|
75 sockthread = threading.Thread(target=manage_socket, |
|
76 name='SockThread', |
|
77 args=((LOCALHOST, port),)) |
|
78 sockthread.setDaemon(True) |
|
79 sockthread.start() |
|
80 while 1: |
|
81 try: |
|
82 if exit_now: |
|
83 try: |
|
84 exit() |
|
85 except KeyboardInterrupt: |
|
86 # exiting but got an extra KBI? Try again! |
|
87 continue |
|
88 try: |
|
89 seq, request = rpc.request_queue.get(block=True, timeout=0.05) |
|
90 except Queue.Empty: |
|
91 continue |
|
92 method, args, kwargs = request |
|
93 ret = method(*args, **kwargs) |
|
94 rpc.response_queue.put((seq, ret)) |
|
95 except KeyboardInterrupt: |
|
96 if quitting: |
|
97 exit_now = True |
|
98 continue |
|
99 except SystemExit: |
|
100 raise |
|
101 except: |
|
102 type, value, tb = sys.exc_info() |
|
103 try: |
|
104 print_exception() |
|
105 rpc.response_queue.put((seq, None)) |
|
106 except: |
|
107 # Link didn't work, print same exception to __stderr__ |
|
108 traceback.print_exception(type, value, tb, file=sys.__stderr__) |
|
109 exit() |
|
110 else: |
|
111 continue |
|
112 |
|
113 def manage_socket(address): |
|
114 for i in range(3): |
|
115 time.sleep(i) |
|
116 try: |
|
117 server = MyRPCServer(address, MyHandler) |
|
118 break |
|
119 except socket.error, err: |
|
120 print>>sys.__stderr__,"IDLE Subprocess: socket error: "\ |
|
121 + err[1] + ", retrying...." |
|
122 else: |
|
123 print>>sys.__stderr__, "IDLE Subprocess: Connection to "\ |
|
124 "IDLE GUI failed, exiting." |
|
125 show_socket_error(err, address) |
|
126 global exit_now |
|
127 exit_now = True |
|
128 return |
|
129 server.handle_request() # A single request only |
|
130 |
|
131 def show_socket_error(err, address): |
|
132 import Tkinter |
|
133 import tkMessageBox |
|
134 root = Tkinter.Tk() |
|
135 root.withdraw() |
|
136 if err[0] == 61: # connection refused |
|
137 msg = "IDLE's subprocess can't connect to %s:%d. This may be due "\ |
|
138 "to your personal firewall configuration. It is safe to "\ |
|
139 "allow this internal connection because no data is visible on "\ |
|
140 "external ports." % address |
|
141 tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root) |
|
142 else: |
|
143 tkMessageBox.showerror("IDLE Subprocess Error", "Socket Error: %s" % err[1]) |
|
144 root.destroy() |
|
145 |
|
146 def print_exception(): |
|
147 import linecache |
|
148 linecache.checkcache() |
|
149 flush_stdout() |
|
150 efile = sys.stderr |
|
151 typ, val, tb = excinfo = sys.exc_info() |
|
152 sys.last_type, sys.last_value, sys.last_traceback = excinfo |
|
153 tbe = traceback.extract_tb(tb) |
|
154 print>>efile, '\nTraceback (most recent call last):' |
|
155 exclude = ("run.py", "rpc.py", "threading.py", "Queue.py", |
|
156 "RemoteDebugger.py", "bdb.py") |
|
157 cleanup_traceback(tbe, exclude) |
|
158 traceback.print_list(tbe, file=efile) |
|
159 lines = traceback.format_exception_only(typ, val) |
|
160 for line in lines: |
|
161 print>>efile, line, |
|
162 |
|
163 def cleanup_traceback(tb, exclude): |
|
164 "Remove excluded traces from beginning/end of tb; get cached lines" |
|
165 orig_tb = tb[:] |
|
166 while tb: |
|
167 for rpcfile in exclude: |
|
168 if tb[0][0].count(rpcfile): |
|
169 break # found an exclude, break for: and delete tb[0] |
|
170 else: |
|
171 break # no excludes, have left RPC code, break while: |
|
172 del tb[0] |
|
173 while tb: |
|
174 for rpcfile in exclude: |
|
175 if tb[-1][0].count(rpcfile): |
|
176 break |
|
177 else: |
|
178 break |
|
179 del tb[-1] |
|
180 if len(tb) == 0: |
|
181 # exception was in IDLE internals, don't prune! |
|
182 tb[:] = orig_tb[:] |
|
183 print>>sys.stderr, "** IDLE Internal Exception: " |
|
184 rpchandler = rpc.objecttable['exec'].rpchandler |
|
185 for i in range(len(tb)): |
|
186 fn, ln, nm, line = tb[i] |
|
187 if nm == '?': |
|
188 nm = "-toplevel-" |
|
189 if not line and fn.startswith("<pyshell#"): |
|
190 line = rpchandler.remotecall('linecache', 'getline', |
|
191 (fn, ln), {}) |
|
192 tb[i] = fn, ln, nm, line |
|
193 |
|
194 def flush_stdout(): |
|
195 try: |
|
196 if sys.stdout.softspace: |
|
197 sys.stdout.softspace = 0 |
|
198 sys.stdout.write("\n") |
|
199 except (AttributeError, EOFError): |
|
200 pass |
|
201 |
|
202 def exit(): |
|
203 """Exit subprocess, possibly after first deleting sys.exitfunc |
|
204 |
|
205 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any |
|
206 sys.exitfunc will be removed before exiting. (VPython support) |
|
207 |
|
208 """ |
|
209 if no_exitfunc: |
|
210 try: |
|
211 del sys.exitfunc |
|
212 except AttributeError: |
|
213 pass |
|
214 sys.exit(0) |
|
215 |
|
216 class MyRPCServer(rpc.RPCServer): |
|
217 |
|
218 def handle_error(self, request, client_address): |
|
219 """Override RPCServer method for IDLE |
|
220 |
|
221 Interrupt the MainThread and exit server if link is dropped. |
|
222 |
|
223 """ |
|
224 global quitting |
|
225 try: |
|
226 raise |
|
227 except SystemExit: |
|
228 raise |
|
229 except EOFError: |
|
230 global exit_now |
|
231 exit_now = True |
|
232 thread.interrupt_main() |
|
233 except: |
|
234 erf = sys.__stderr__ |
|
235 print>>erf, '\n' + '-'*40 |
|
236 print>>erf, 'Unhandled server exception!' |
|
237 print>>erf, 'Thread: %s' % threading.currentThread().getName() |
|
238 print>>erf, 'Client Address: ', client_address |
|
239 print>>erf, 'Request: ', repr(request) |
|
240 traceback.print_exc(file=erf) |
|
241 print>>erf, '\n*** Unrecoverable, server exiting!' |
|
242 print>>erf, '-'*40 |
|
243 quitting = True |
|
244 thread.interrupt_main() |
|
245 |
|
246 |
|
247 class MyHandler(rpc.RPCHandler): |
|
248 |
|
249 def handle(self): |
|
250 """Override base method""" |
|
251 executive = Executive(self) |
|
252 self.register("exec", executive) |
|
253 sys.stdin = self.console = self.get_remote_proxy("stdin") |
|
254 sys.stdout = self.get_remote_proxy("stdout") |
|
255 sys.stderr = self.get_remote_proxy("stderr") |
|
256 import IOBinding |
|
257 sys.stdin.encoding = sys.stdout.encoding = \ |
|
258 sys.stderr.encoding = IOBinding.encoding |
|
259 self.interp = self.get_remote_proxy("interp") |
|
260 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) |
|
261 |
|
262 def exithook(self): |
|
263 "override SocketIO method - wait for MainThread to shut us down" |
|
264 time.sleep(10) |
|
265 |
|
266 def EOFhook(self): |
|
267 "Override SocketIO method - terminate wait on callback and exit thread" |
|
268 global quitting |
|
269 quitting = True |
|
270 thread.interrupt_main() |
|
271 |
|
272 def decode_interrupthook(self): |
|
273 "interrupt awakened thread" |
|
274 global quitting |
|
275 quitting = True |
|
276 thread.interrupt_main() |
|
277 |
|
278 |
|
279 class Executive(object): |
|
280 |
|
281 def __init__(self, rpchandler): |
|
282 self.rpchandler = rpchandler |
|
283 self.locals = __main__.__dict__ |
|
284 self.calltip = CallTips.CallTips() |
|
285 self.autocomplete = AutoComplete.AutoComplete() |
|
286 |
|
287 def runcode(self, code): |
|
288 global interruptable |
|
289 try: |
|
290 self.usr_exc_info = None |
|
291 interruptable = True |
|
292 try: |
|
293 exec code in self.locals |
|
294 finally: |
|
295 interruptable = False |
|
296 except: |
|
297 self.usr_exc_info = sys.exc_info() |
|
298 if quitting: |
|
299 exit() |
|
300 # even print a user code SystemExit exception, continue |
|
301 print_exception() |
|
302 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") |
|
303 if jit: |
|
304 self.rpchandler.interp.open_remote_stack_viewer() |
|
305 else: |
|
306 flush_stdout() |
|
307 |
|
308 def interrupt_the_server(self): |
|
309 if interruptable: |
|
310 thread.interrupt_main() |
|
311 |
|
312 def start_the_debugger(self, gui_adap_oid): |
|
313 return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid) |
|
314 |
|
315 def stop_the_debugger(self, idb_adap_oid): |
|
316 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" |
|
317 self.rpchandler.unregister(idb_adap_oid) |
|
318 |
|
319 def get_the_calltip(self, name): |
|
320 return self.calltip.fetch_tip(name) |
|
321 |
|
322 def get_the_completion_list(self, what, mode): |
|
323 return self.autocomplete.fetch_completions(what, mode) |
|
324 |
|
325 def stackviewer(self, flist_oid=None): |
|
326 if self.usr_exc_info: |
|
327 typ, val, tb = self.usr_exc_info |
|
328 else: |
|
329 return None |
|
330 flist = None |
|
331 if flist_oid is not None: |
|
332 flist = self.rpchandler.get_remote_proxy(flist_oid) |
|
333 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: |
|
334 tb = tb.tb_next |
|
335 sys.last_type = typ |
|
336 sys.last_value = val |
|
337 item = StackViewer.StackTreeItem(flist, tb) |
|
338 return RemoteObjectBrowser.remote_object_tree_item(item) |