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