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