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