Home | History | Annotate | Download | only in idlelib
      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 from idlelib import CallTips
     11 from idlelib import AutoComplete
     12 
     13 from idlelib import RemoteDebugger
     14 from idlelib import RemoteObjectBrowser
     15 from idlelib import StackViewer
     16 from idlelib import rpc
     17 from idlelib import PyShell
     18 from idlelib import IOBinding
     19 
     20 import __main__
     21 
     22 LOCALHOST = '127.0.0.1'
     23 
     24 import warnings
     25 
     26 def 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 
     42 def 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 
     55 capture_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 
     61 exit_now = False
     62 quitting = False
     63 interruptable = False
     64 
     65 def 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 
    136 def 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 
    154 def 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 
    170 def 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 
    187 def 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 
    220 def 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 
    228 def 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 
    243 class 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 
    273 class 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 
    311 class 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 
    375 capture_warnings(False)  # Make sure turned off; see issue 18081
    376