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