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.

M NEWS.txt
M PyShell.py
M ScriptBinding.py
M run.py

Backport candidate.
This commit is contained in:
Kurt B. Kaiser 2004-01-21 18:54:30 +00:00
parent 1fe9750200
commit af3eb87802
4 changed files with 96 additions and 56 deletions

View File

@ -3,6 +3,12 @@ What's New in IDLE 1.1a0?
*Release date: XX-XXX-2004* *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 - Print correct exception even if source file changed since shell was
restarted. IDLEfork Patch 869012 Noam Raphael restarted. IDLEfork Patch 869012 Noam Raphael

View File

@ -251,7 +251,9 @@ class PyShellFileList(FileList):
self.pyshell.wakeup() self.pyshell.wakeup()
else: else:
self.pyshell = PyShell(self) self.pyshell = PyShell(self)
self.pyshell.begin() if self.pyshell:
if not self.pyshell.begin():
return None
return self.pyshell return self.pyshell
@ -344,6 +346,9 @@ class ModifiedInterpreter(InteractiveInterpreter):
return [decorated_exec] + w + ["-c", command, str(self.port)] return [decorated_exec] + w + ["-c", command, str(self.port)]
def start_subprocess(self): 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) addr = (LOCALHOST, self.port)
# Idle starts listening for connection on localhost # Idle starts listening for connection on localhost
for i in range(3): for i in range(3):
@ -352,14 +357,17 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.rpcclt = MyRPCClient(addr) self.rpcclt = MyRPCClient(addr)
break break
except socket.error, err: except socket.error, err:
print>>sys.__stderr__,"IDLE socket error: " + err[1]\ pass
+ ", retrying..."
else: else:
display_port_binding_error() self.display_port_binding_error()
sys.exit() return None
self.spawn_subprocess()
# Accept the connection from the Python execution server # 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("stdin", self.tkconsole)
self.rpcclt.register("stdout", self.tkconsole.stdout) self.rpcclt.register("stdout", self.tkconsole.stdout)
self.rpcclt.register("stderr", self.tkconsole.stderr) self.rpcclt.register("stderr", self.tkconsole.stderr)
@ -368,10 +376,11 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.rpcclt.register("interp", self) self.rpcclt.register("interp", self)
self.transfer_path() self.transfer_path()
self.poll_subprocess() self.poll_subprocess()
return self.rpcclt
def restart_subprocess(self): def restart_subprocess(self):
if self.restarting: if self.restarting:
return return self.rpcclt
self.restarting = True self.restarting = True
# close only the subprocess debugger # close only the subprocess debugger
debug = self.getdebugger() debug = self.getdebugger()
@ -388,7 +397,11 @@ class ModifiedInterpreter(InteractiveInterpreter):
was_executing = console.executing was_executing = console.executing
console.executing = False console.executing = False
self.spawn_subprocess() self.spawn_subprocess()
self.rpcclt.accept() try:
self.rpcclt.accept()
except socket.timeout, err:
self.display_no_subprocess_error()
return None
self.transfer_path() self.transfer_path()
# annotate restart in shell window and mark it # annotate restart in shell window and mark it
console.text.delete("iomark", "end-1c") console.text.delete("iomark", "end-1c")
@ -407,6 +420,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
# reload remote debugger breakpoints for all PyShellEditWindows # reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints() debug.load_breakpoints()
self.restarting = False self.restarting = False
return self.rpcclt
def __request_interrupt(self): def __request_interrupt(self):
self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
@ -415,7 +429,10 @@ class ModifiedInterpreter(InteractiveInterpreter):
threading.Thread(target=self.__request_interrupt).start() threading.Thread(target=self.__request_interrupt).start()
def kill_subprocess(self): def kill_subprocess(self):
self.rpcclt.close() try:
self.rpcclt.close()
except AttributeError: # no socket
pass
self.unix_terminate() self.unix_terminate()
self.tkconsole.executing = False self.tkconsole.executing = False
self.rpcclt = None self.rpcclt = None
@ -638,13 +655,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
if key[:1] + key[-1:] != "<>": if key[:1] + key[-1:] != "<>":
del c[key] 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): def runcommand(self, code):
"Run the code without invoking the debugger" "Run the code without invoking the debugger"
# The code better not raise an exception! # The code better not raise an exception!
@ -695,6 +705,34 @@ class ModifiedInterpreter(InteractiveInterpreter):
"Override base class method" "Override base class method"
self.tkconsole.stderr.write(s) 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): class PyShell(OutputWindow):
shell_title = "Python Shell" shell_title = "Python Shell"
@ -765,8 +803,6 @@ class PyShell(OutputWindow):
self.history = self.History(self.text) self.history = self.History(self.text)
# #
self.pollinterval = 50 # millisec self.pollinterval = 50 # millisec
if use_subprocess:
self.interp.start_subprocess()
reading = False reading = False
executing = False executing = False
@ -887,6 +923,10 @@ class PyShell(OutputWindow):
self.resetoutput() self.resetoutput()
if use_subprocess: if use_subprocess:
nosub = '' nosub = ''
client = self.interp.start_subprocess()
if not client:
self.close()
return None
else: else:
nosub = "==== No Subprocess ====" nosub = "==== No Subprocess ===="
self.write("Python %s on %s\n%s\n%s\nIDLE %s %s\n" % 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.firewallmessage, idlever.IDLE_VERSION, nosub))
self.showprompt() self.showprompt()
import Tkinter import Tkinter
Tkinter._default_root = None Tkinter._default_root = None # 03Jan04 KBK What's this?
return client
def interact(self):
self.begin()
self.top.mainloop()
def readline(self): def readline(self):
save = self.reading save = self.reading
@ -1281,11 +1318,9 @@ def main():
flist.open(filename) flist.open(filename)
if not args: if not args:
flist.new() flist.new()
if enable_shell: if enable_shell:
flist.open_shell() if not flist.open_shell():
elif enable_shell: return # couldn't open shell
flist.pyshell = PyShell(flist)
flist.pyshell.begin()
shell = flist.pyshell shell = flist.pyshell
# handle remaining options: # handle remaining options:
if debug: if debug:
@ -1295,7 +1330,7 @@ def main():
os.environ.get("PYTHONSTARTUP") os.environ.get("PYTHONSTARTUP")
if filename and os.path.isfile(filename): if filename and os.path.isfile(filename):
shell.interp.execfile(filename) shell.interp.execfile(filename)
if cmd or script: if shell and cmd or script:
shell.interp.runcommand("""if 1: shell.interp.runcommand("""if 1:
import sys as _sys import sys as _sys
_sys.argv = %s _sys.argv = %s
@ -1309,24 +1344,6 @@ def main():
root.mainloop() root.mainloop()
root.destroy() 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__": if __name__ == "__main__":
sys.modules['PyShell'] = sys.modules['__main__'] sys.modules['PyShell'] = sys.modules['__main__']
main() main()

View File

@ -137,6 +137,8 @@ class ScriptBinding:
return return
flist = self.editwin.flist flist = self.editwin.flist
shell = flist.open_shell() shell = flist.open_shell()
if not shell:
return # couldn't open the shell
interp = shell.interp interp = shell.interp
if PyShell.use_subprocess: if PyShell.use_subprocess:
shell.restart_shell() shell.restart_shell()

View File

@ -47,6 +47,7 @@ def main(del_exitfunc=False):
global no_exitfunc global no_exitfunc
no_exitfunc = del_exitfunc no_exitfunc = del_exitfunc
port = 8833 port = 8833
#time.sleep(15) # test subprocess not responding
if sys.argv[1:]: if sys.argv[1:]:
port = int(sys.argv[1]) port = int(sys.argv[1])
sys.argv[:] = [""] sys.argv[:] = [""]
@ -90,24 +91,38 @@ def main(del_exitfunc=False):
continue continue
def manage_socket(address): def manage_socket(address):
for i in range(6): for i in range(3):
time.sleep(i) time.sleep(i)
try: try:
server = MyRPCServer(address, MyHandler) server = MyRPCServer(address, MyHandler)
break break
except socket.error, err: except socket.error, err:
if i < 3: print>>sys.__stderr__,"IDLE Subprocess: socket error: "\
print>>sys.__stderr__, ".. ", + err[1] + ", retrying...."
else:
print>>sys.__stderr__,"\nPython subprocess socket error: "\
+ err[1] + ", retrying...."
else: 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 global exit_now
exit_now = True exit_now = True
return return
server.handle_request() # A single request only 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(): def print_exception():
import linecache import linecache
linecache.checkcache() linecache.checkcache()
@ -116,7 +131,7 @@ def print_exception():
typ, val, tb = excinfo = sys.exc_info() typ, val, tb = excinfo = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = excinfo sys.last_type, sys.last_value, sys.last_traceback = excinfo
tbe = traceback.extract_tb(tb) 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", exclude = ("run.py", "rpc.py", "threading.py", "Queue.py",
"RemoteDebugger.py", "bdb.py") "RemoteDebugger.py", "bdb.py")
cleanup_traceback(tbe, exclude) cleanup_traceback(tbe, exclude)