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