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