diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 998bbaa6f02..7f52564f319 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,12 @@ What's New in IDLE 1.1a0? *Release date: XX-XXX-2004* +- Added a Tk error dialog to run.py inform the user if the subprocess can't + connect to the user GUI process. Added a timeout to the GUI's listening + socket. Added Tk error dialogs to PyShell.py to announce a failure to bind + the port or connect to the subprocess. Clean up error handling during + connection initiation phase. This is an update of Python Patch 778323. + - Print correct exception even if source file changed since shell was restarted. IDLEfork Patch 869012 Noam Raphael diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py index b6205ca6894..fe940cb9aa9 100644 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/PyShell.py @@ -251,7 +251,9 @@ class PyShellFileList(FileList): self.pyshell.wakeup() else: self.pyshell = PyShell(self) - self.pyshell.begin() + if self.pyshell: + if not self.pyshell.begin(): + return None return self.pyshell @@ -344,6 +346,9 @@ class ModifiedInterpreter(InteractiveInterpreter): return [decorated_exec] + w + ["-c", command, str(self.port)] def start_subprocess(self): + # spawning first avoids passing a listening socket to the subprocess + self.spawn_subprocess() + #time.sleep(20) # test to simulate GUI not accepting connection addr = (LOCALHOST, self.port) # Idle starts listening for connection on localhost for i in range(3): @@ -352,14 +357,17 @@ class ModifiedInterpreter(InteractiveInterpreter): self.rpcclt = MyRPCClient(addr) break except socket.error, err: - print>>sys.__stderr__,"IDLE socket error: " + err[1]\ - + ", retrying..." + pass else: - display_port_binding_error() - sys.exit() - self.spawn_subprocess() + self.display_port_binding_error() + return None # Accept the connection from the Python execution server - self.rpcclt.accept() + self.rpcclt.listening_sock.settimeout(10) + try: + self.rpcclt.accept() + except socket.timeout, err: + self.display_no_subprocess_error() + return None self.rpcclt.register("stdin", self.tkconsole) self.rpcclt.register("stdout", self.tkconsole.stdout) self.rpcclt.register("stderr", self.tkconsole.stderr) @@ -368,10 +376,11 @@ class ModifiedInterpreter(InteractiveInterpreter): self.rpcclt.register("interp", self) self.transfer_path() self.poll_subprocess() + return self.rpcclt def restart_subprocess(self): if self.restarting: - return + return self.rpcclt self.restarting = True # close only the subprocess debugger debug = self.getdebugger() @@ -388,7 +397,11 @@ class ModifiedInterpreter(InteractiveInterpreter): was_executing = console.executing console.executing = False self.spawn_subprocess() - self.rpcclt.accept() + try: + self.rpcclt.accept() + except socket.timeout, err: + self.display_no_subprocess_error() + return None self.transfer_path() # annotate restart in shell window and mark it console.text.delete("iomark", "end-1c") @@ -407,6 +420,7 @@ class ModifiedInterpreter(InteractiveInterpreter): # reload remote debugger breakpoints for all PyShellEditWindows debug.load_breakpoints() self.restarting = False + return self.rpcclt def __request_interrupt(self): self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) @@ -415,7 +429,10 @@ class ModifiedInterpreter(InteractiveInterpreter): threading.Thread(target=self.__request_interrupt).start() def kill_subprocess(self): - self.rpcclt.close() + try: + self.rpcclt.close() + except AttributeError: # no socket + pass self.unix_terminate() self.tkconsole.executing = False self.rpcclt = None @@ -638,13 +655,6 @@ class ModifiedInterpreter(InteractiveInterpreter): if key[:1] + key[-1:] != "<>": del c[key] - def display_executing_dialog(self): - tkMessageBox.showerror( - "Already executing", - "The Python Shell window is already executing a command; " - "please wait until it is finished.", - master=self.tkconsole.text) - def runcommand(self, code): "Run the code without invoking the debugger" # The code better not raise an exception! @@ -695,6 +705,34 @@ class ModifiedInterpreter(InteractiveInterpreter): "Override base class method" self.tkconsole.stderr.write(s) + def display_port_binding_error(self): + tkMessageBox.showerror( + "Port Binding Error", + "IDLE can't bind TCP/IP port 8833, which is necessary to " + "communicate with its Python execution server. Either " + "no networking is installed on this computer or another " + "process (another IDLE?) is using the port. Run IDLE with the -n " + "command line switch to start without a subprocess and refer to " + "Help/IDLE Help 'Running without a subprocess' for further " + "details.", + master=self.tkconsole.text) + + def display_no_subprocess_error(self): + tkMessageBox.showerror( + "Subprocess Startup Error", + "IDLE's subprocess didn't make connection. Either IDLE can't " + "start a subprocess or personal firewall software is blocking " + "the connection.", + master=self.tkconsole.text) + + def display_executing_dialog(self): + tkMessageBox.showerror( + "Already executing", + "The Python Shell window is already executing a command; " + "please wait until it is finished.", + master=self.tkconsole.text) + + class PyShell(OutputWindow): shell_title = "Python Shell" @@ -765,8 +803,6 @@ class PyShell(OutputWindow): self.history = self.History(self.text) # self.pollinterval = 50 # millisec - if use_subprocess: - self.interp.start_subprocess() reading = False executing = False @@ -887,6 +923,10 @@ class PyShell(OutputWindow): self.resetoutput() if use_subprocess: nosub = '' + client = self.interp.start_subprocess() + if not client: + self.close() + return None else: nosub = "==== No Subprocess ====" self.write("Python %s on %s\n%s\n%s\nIDLE %s %s\n" % @@ -894,11 +934,8 @@ class PyShell(OutputWindow): self.firewallmessage, idlever.IDLE_VERSION, nosub)) self.showprompt() import Tkinter - Tkinter._default_root = None - - def interact(self): - self.begin() - self.top.mainloop() + Tkinter._default_root = None # 03Jan04 KBK What's this? + return client def readline(self): save = self.reading @@ -1281,11 +1318,9 @@ def main(): flist.open(filename) if not args: flist.new() - if enable_shell: - flist.open_shell() - elif enable_shell: - flist.pyshell = PyShell(flist) - flist.pyshell.begin() + if enable_shell: + if not flist.open_shell(): + return # couldn't open shell shell = flist.pyshell # handle remaining options: if debug: @@ -1295,7 +1330,7 @@ def main(): os.environ.get("PYTHONSTARTUP") if filename and os.path.isfile(filename): shell.interp.execfile(filename) - if cmd or script: + if shell and cmd or script: shell.interp.runcommand("""if 1: import sys as _sys _sys.argv = %s @@ -1309,24 +1344,6 @@ def main(): root.mainloop() root.destroy() - -def display_port_binding_error(): - print """\ -\nIDLE cannot run. - -IDLE needs to use a specific TCP/IP port (8833) in order to communicate with -its Python execution server. IDLE is unable to bind to this port, and so -cannot start. Here are some possible causes of this problem: - - 1. TCP/IP networking is not installed or not working on this computer - 2. Another program (another IDLE?) is running that uses this port - 3. Personal firewall software is preventing IDLE from using this port - -Run IDLE with the -n command line switch to start without a subprocess -and refer to Help/IDLE Help "Running without a subprocess" for further -details. -""" - if __name__ == "__main__": sys.modules['PyShell'] = sys.modules['__main__'] main() diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py index fa9ecbc5234..c0fa88f4266 100644 --- a/Lib/idlelib/ScriptBinding.py +++ b/Lib/idlelib/ScriptBinding.py @@ -137,6 +137,8 @@ class ScriptBinding: return flist = self.editwin.flist shell = flist.open_shell() + if not shell: + return # couldn't open the shell interp = shell.interp if PyShell.use_subprocess: shell.restart_shell() diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 8cfa808b14a..96da4593b47 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -47,6 +47,7 @@ def main(del_exitfunc=False): global no_exitfunc no_exitfunc = del_exitfunc port = 8833 + #time.sleep(15) # test subprocess not responding if sys.argv[1:]: port = int(sys.argv[1]) sys.argv[:] = [""] @@ -90,24 +91,38 @@ def main(del_exitfunc=False): continue def manage_socket(address): - for i in range(6): + for i in range(3): time.sleep(i) try: server = MyRPCServer(address, MyHandler) break except socket.error, err: - if i < 3: - print>>sys.__stderr__, ".. ", - else: - print>>sys.__stderr__,"\nPython subprocess socket error: "\ - + err[1] + ", retrying...." + print>>sys.__stderr__,"IDLE Subprocess: socket error: "\ + + err[1] + ", retrying...." else: - print>>sys.__stderr__, "\nConnection to Idle failed, exiting." + print>>sys.__stderr__, "IDLE Subprocess: Connection to "\ + "IDLE GUI failed, exiting." + show_socket_error(err, address) global exit_now exit_now = True return server.handle_request() # A single request only +def show_socket_error(err, address): + import Tkinter + import tkMessageBox + root = Tkinter.Tk() + root.withdraw() + if err[0] == 61: # connection refused + 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: + tkMessageBox.showerror("IDLE Subprocess Error", "Socket Error: %s" % err[1]) + root.destroy() + def print_exception(): import linecache linecache.checkcache() @@ -116,7 +131,7 @@ def print_exception(): typ, val, tb = excinfo = sys.exc_info() sys.last_type, sys.last_value, sys.last_traceback = excinfo tbe = traceback.extract_tb(tb) - print >>efile, '\nTraceback (most recent call last):' + print>>efile, '\nTraceback (most recent call last):' exclude = ("run.py", "rpc.py", "threading.py", "Queue.py", "RemoteDebugger.py", "bdb.py") cleanup_traceback(tbe, exclude)