run.py 12.7 KB
Newer Older
Chui Tey's avatar
Chui Tey committed
1
import sys
2
import io
3
import linecache
4 5
import time
import socket
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
6
import traceback
7
import _thread as thread
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
8
import threading
9
import queue
10

11 12
from idlelib import CallTips
from idlelib import AutoComplete
13

14 15 16 17
from idlelib import RemoteDebugger
from idlelib import RemoteObjectBrowser
from idlelib import StackViewer
from idlelib import rpc
Chui Tey's avatar
Chui Tey committed
18

19 20
import __main__

21 22
LOCALHOST = '127.0.0.1'

23 24 25 26 27
try:
    import warnings
except ImportError:
    pass
else:
Benjamin Peterson's avatar
Benjamin Peterson committed
28
    def idle_formatwarning_subproc(message, category, filename, lineno,
29
                                   line=None):
30 31 32
        """Format warnings the IDLE way"""
        s = "\nWarning (from warnings module):\n"
        s += '  File \"%s\", line %s\n' % (filename, lineno)
33 34 35
        if line is None:
            line = linecache.getline(filename, lineno)
        line = line.strip()
36 37 38 39 40 41
        if line:
            s += "    %s\n" % line
        s += "%s: %s\n" % (category.__name__, message)
        return s
    warnings.formatwarning = idle_formatwarning_subproc

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
42 43
# Thread shared globals: Establish a queue between a subthread (which handles
# the socket) and the main thread (which runs user code), plus global
44
# completion, exit and interruptable (the main thread) flags:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
45

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
46 47
exit_now = False
quitting = False
48
interruptable = False
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
49

50
def main(del_exitfunc=False):
51 52
    """Start the Python execution server in a subprocess

53 54 55
    In the Python subprocess, RPCServer is instantiated with handlerclass
    MyHandler, which inherits register/unregister methods from RPCHandler via
    the mix-in class SocketIO.
56

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
57
    When the RPCServer 'server' is instantiated, the TCPServer initialization
58 59 60 61 62 63 64 65
    creates an instance of run.MyHandler and calls its handle() method.
    handle() instantiates a run.Executive object, passing it a reference to the
    MyHandler object.  That reference is saved as attribute rpchandler of the
    Executive instance.  The Executive methods have access to the reference and
    can pass it on to entities that they command
    (e.g. RemoteDebugger.Debugger.start_debugger()).  The latter, in turn, can
    call MyHandler(SocketIO) register/unregister methods via the reference to
    register and unregister themselves.
66 67

    """
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
68 69
    global exit_now
    global quitting
70 71
    global no_exitfunc
    no_exitfunc = del_exitfunc
72
    #time.sleep(15) # test subprocess not responding
73 74 75 76
    try:
        assert(len(sys.argv) > 1)
        port = int(sys.argv[-1])
    except:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
77 78
        print("IDLE Subprocess: no IP port passed in sys.argv.",
              file=sys.__stderr__)
79
        return
Chui Tey's avatar
Chui Tey committed
80
    sys.argv[:] = [""]
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
81 82
    sockthread = threading.Thread(target=manage_socket,
                                  name='SockThread',
83
                                  args=((LOCALHOST, port),))
84
    sockthread.daemon = True
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
85 86 87
    sockthread.start()
    while 1:
        try:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
88 89
            if exit_now:
                try:
90
                    exit()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
91 92 93
                except KeyboardInterrupt:
                    # exiting but got an extra KBI? Try again!
                    continue
94
            try:
95
                seq, request = rpc.request_queue.get(block=True, timeout=0.05)
96
            except queue.Empty:
97 98 99 100
                continue
            method, args, kwargs = request
            ret = method(*args, **kwargs)
            rpc.response_queue.put((seq, ret))
101
        except KeyboardInterrupt:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
102 103
            if quitting:
                exit_now = True
104
            continue
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
105 106
        except SystemExit:
            raise
107
        except:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
108
            type, value, tb = sys.exc_info()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
109 110 111 112
            try:
                print_exception()
                rpc.response_queue.put((seq, None))
            except:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
113 114
                # Link didn't work, print same exception to __stderr__
                traceback.print_exception(type, value, tb, file=sys.__stderr__)
115
                exit()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
116 117
            else:
                continue
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
118 119

def manage_socket(address):
120
    for i in range(3):
121 122
        time.sleep(i)
        try:
123
            server = MyRPCServer(address, MyHandler)
124
            break
125
        except socket.error as err:
126 127
            print("IDLE Subprocess: socket error: " + err.args[1] +
                  ", retrying....", file=sys.__stderr__)
128
            socket_error = err
129
    else:
130 131 132
        print("IDLE Subprocess: Connection to "
              "IDLE GUI failed, exiting.", file=sys.__stderr__)
        show_socket_error(socket_error, address)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
133 134
        global exit_now
        exit_now = True
135
        return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
136 137
    server.handle_request() # A single request only

138
def show_socket_error(err, address):
139 140 141
    import tkinter
    import tkinter.messagebox as tkMessageBox
    root = tkinter.Tk()
142
    root.withdraw()
143
    if err.args[0] == 61: # connection refused
144 145 146 147 148 149
        msg = "IDLE's subprocess can't connect to %s:%d.  This may be due "\
              "to your personal firewall configuration.  It is safe to "\
              "allow this internal connection because no data is visible on "\
              "external ports." % address
        tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root)
    else:
150 151
        tkMessageBox.showerror("IDLE Subprocess Error",
                               "Socket Error: %s" % err.args[1])
152 153
    root.destroy()

154
def print_exception():
155 156
    import linecache
    linecache.checkcache()
157 158
    flush_stdout()
    efile = sys.stderr
159 160
    typ, val, tb = excinfo = sys.exc_info()
    sys.last_type, sys.last_value, sys.last_traceback = excinfo
161
    tbe = traceback.extract_tb(tb)
162
    print('Traceback (most recent call last):', file=efile)
Georg Brandl's avatar
Georg Brandl committed
163
    exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
164 165 166 167 168
               "RemoteDebugger.py", "bdb.py")
    cleanup_traceback(tbe, exclude)
    traceback.print_list(tbe, file=efile)
    lines = traceback.format_exception_only(typ, val)
    for line in lines:
169
        print(line, end='', file=efile)
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

def cleanup_traceback(tb, exclude):
    "Remove excluded traces from beginning/end of tb; get cached lines"
    orig_tb = tb[:]
    while tb:
        for rpcfile in exclude:
            if tb[0][0].count(rpcfile):
                break    # found an exclude, break for: and delete tb[0]
        else:
            break        # no excludes, have left RPC code, break while:
        del tb[0]
    while tb:
        for rpcfile in exclude:
            if tb[-1][0].count(rpcfile):
                break
        else:
            break
        del tb[-1]
    if len(tb) == 0:
        # exception was in IDLE internals, don't prune!
        tb[:] = orig_tb[:]
191
        print("** IDLE Internal Exception: ", file=sys.stderr)
192 193 194 195 196 197 198 199 200 201 202
    rpchandler = rpc.objecttable['exec'].rpchandler
    for i in range(len(tb)):
        fn, ln, nm, line = tb[i]
        if nm == '?':
            nm = "-toplevel-"
        if not line and fn.startswith("<pyshell#"):
            line = rpchandler.remotecall('linecache', 'getline',
                                              (fn, ln), {})
        tb[i] = fn, ln, nm, line

def flush_stdout():
203
    """XXX How to do this now?"""
204

205
def exit():
206
    """Exit subprocess, possibly after first clearing exit functions.
207 208

    If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
209 210
    functions registered with atexit will be removed before exiting.
    (VPython support)
211 212 213

    """
    if no_exitfunc:
214 215
        import atexit
        atexit._clear()
216
    sys.exit(0)
Chui Tey's avatar
Chui Tey committed
217

218 219 220 221 222 223 224 225
class MyRPCServer(rpc.RPCServer):

    def handle_error(self, request, client_address):
        """Override RPCServer method for IDLE

        Interrupt the MainThread and exit server if link is dropped.

        """
226
        global quitting
227 228 229 230 231
        try:
            raise
        except SystemExit:
            raise
        except EOFError:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
232 233
            global exit_now
            exit_now = True
234
            thread.interrupt_main()
235 236
        except:
            erf = sys.__stderr__
237 238
            print('\n' + '-'*40, file=erf)
            print('Unhandled server exception!', file=erf)
239
            print('Thread: %s' % threading.current_thread().name, file=erf)
240 241
            print('Client Address: ', client_address, file=erf)
            print('Request: ', repr(request), file=erf)
242
            traceback.print_exc(file=erf)
243 244
            print('\n*** Unrecoverable, server exiting!', file=erf)
            print('-'*40, file=erf)
245 246
            quitting = True
            thread.interrupt_main()
247

248
class _RPCFile(io.TextIOBase):
Martin v. Löwis's avatar
Martin v. Löwis committed
249
    """Wrapper class for the RPC proxy to typecheck arguments
250 251 252
    that may not support pickling. The base class is there only
    to support type tests; all implementations come from the remote
    object."""
253 254 255 256

    def __init__(self, rpc):
        super.__setattr__(self, 'rpc', rpc)

257
    def __getattribute__(self, name):
258
        # When accessing the 'rpc' attribute, or 'write', use ours
259
        if name in ('rpc', 'write', 'writelines'):
260 261
            return io.TextIOBase.__getattribute__(self, name)
        # Else only look into the remote object only
262 263 264 265 266
        return getattr(self.rpc, name)

    def __setattr__(self, name, value):
        return setattr(self.rpc, name, value)

267 268 269 270 271 272 273 274 275 276
    @staticmethod
    def _ensure_string(func):
        def f(self, s):
            if not isinstance(s, str):
                raise TypeError('must be str, not ' + type(s).__name__)
            return func(self, s)
        return f

class _RPCOutputFile(_RPCFile):
    @_RPCFile._ensure_string
277 278 279 280
    def write(self, s):
        if not isinstance(s, str):
            raise TypeError('must be str, not ' + type(s).__name__)
        return self.rpc.write(s)
281

282 283 284 285 286 287
class _RPCInputFile(_RPCFile):
    @_RPCFile._ensure_string
    def write(self, s):
        raise io.UnsupportedOperation("not writable")
    writelines = write

Chui Tey's avatar
Chui Tey committed
288 289 290
class MyHandler(rpc.RPCHandler):

    def handle(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
291
        """Override base method"""
Chui Tey's avatar
Chui Tey committed
292 293
        executive = Executive(self)
        self.register("exec", executive)
294 295 296 297
        self.console = self.get_remote_proxy("stdin")
        sys.stdin = _RPCInputFile(self.console)
        sys.stdout = _RPCOutputFile(self.get_remote_proxy("stdout"))
        sys.stderr = _RPCOutputFile(self.get_remote_proxy("stderr"))
298 299 300
        # page help() text to shell.
        import pydoc # import must be done here to capture i/o binding
        pydoc.pager = pydoc.plainpager
301
        from idlelib import IOBinding
302 303
        sys.stdin.encoding = sys.stdout.encoding = \
                             sys.stderr.encoding = IOBinding.encoding
304
        self.interp = self.get_remote_proxy("interp")
305 306 307 308
        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)

    def exithook(self):
        "override SocketIO method - wait for MainThread to shut us down"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
309
        time.sleep(10)
310 311 312

    def EOFhook(self):
        "Override SocketIO method - terminate wait on callback and exit thread"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
313 314
        global quitting
        quitting = True
315
        thread.interrupt_main()
316 317 318

    def decode_interrupthook(self):
        "interrupt awakened thread"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
319 320
        global quitting
        quitting = True
321
        thread.interrupt_main()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
322

Chui Tey's avatar
Chui Tey committed
323

324
class Executive(object):
Chui Tey's avatar
Chui Tey committed
325 326

    def __init__(self, rpchandler):
327
        self.rpchandler = rpchandler
328
        self.locals = __main__.__dict__
329
        self.calltip = CallTips.CallTips()
330
        self.autocomplete = AutoComplete.AutoComplete()
Chui Tey's avatar
Chui Tey committed
331 332

    def runcode(self, code):
333
        global interruptable
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
334
        try:
335
            self.usr_exc_info = None
336 337 338 339 340
            interruptable = True
            try:
                exec(code, self.locals)
            finally:
                interruptable = False
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
341
        except:
342
            self.usr_exc_info = sys.exc_info()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
343
            if quitting:
344
                exit()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
345 346
            # even print a user code SystemExit exception, continue
            print_exception()
347 348 349
            jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
            if jit:
                self.rpchandler.interp.open_remote_stack_viewer()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
350
        else:
351
            flush_stdout()
Chui Tey's avatar
Chui Tey committed
352

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
353
    def interrupt_the_server(self):
354 355
        if interruptable:
            thread.interrupt_main()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
356

357
    def start_the_debugger(self, gui_adap_oid):
358 359 360 361 362
        return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)

    def stop_the_debugger(self, idb_adap_oid):
        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
        self.rpchandler.unregister(idb_adap_oid)
Chui Tey's avatar
Chui Tey committed
363

364 365 366
    def get_the_calltip(self, name):
        return self.calltip.fetch_tip(name)

367 368 369
    def get_the_completion_list(self, what, mode):
        return self.autocomplete.fetch_completions(what, mode)

Chui Tey's avatar
Chui Tey committed
370
    def stackviewer(self, flist_oid=None):
371 372 373
        if self.usr_exc_info:
            typ, val, tb = self.usr_exc_info
        else:
Chui Tey's avatar
Chui Tey committed
374 375 376
            return None
        flist = None
        if flist_oid is not None:
377
            flist = self.rpchandler.get_remote_proxy(flist_oid)
Chui Tey's avatar
Chui Tey committed
378 379
        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
            tb = tb.tb_next
380 381
        sys.last_type = typ
        sys.last_value = val
Chui Tey's avatar
Chui Tey committed
382 383
        item = StackViewer.StackTreeItem(flist, tb)
        return RemoteObjectBrowser.remote_object_tree_item(item)