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)
|
---|