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