summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKurt B. Kaiser <kbk@shore.net>2003-02-17 18:57:16 (GMT)
committerKurt B. Kaiser <kbk@shore.net>2003-02-17 18:57:16 (GMT)
commit003091cd51c5278e3ef76b6db01bd719b8b1c416 (patch)
treeafc0ea2b00d259d40f2eebd7beb33bd96a4b9eba
parentf927f14edab27b128b1962314c1cf7c10be73ac5 (diff)
downloadcpython-003091cd51c5278e3ef76b6db01bd719b8b1c416.zip
cpython-003091cd51c5278e3ef76b6db01bd719b8b1c416.tar.gz
cpython-003091cd51c5278e3ef76b6db01bd719b8b1c416.tar.bz2
M NEWS.txt
M PyShell.py M ScriptBinding.py M rpc.py M run.py Clean up the way IDLEfork handles termination of the subprocess, restore ability to interrupt user code in Windows (so long as it's doing terminal I/O). 1. Handle subprocess interrupts in Windows with an RPC message. 2. Run/F5 will restart the subprocess even if user code is running. 3. Restart the subprocess if the link is dropped. 4. Exit IDLE cleanly even during I/O. 4. In rpc.py, remove explicit calls to statelock, let the condition variable handle acquire() and release().
-rw-r--r--Lib/idlelib/NEWS.txt35
-rw-r--r--Lib/idlelib/PyShell.py109
-rw-r--r--Lib/idlelib/ScriptBinding.py3
-rw-r--r--Lib/idlelib/rpc.py60
-rw-r--r--Lib/idlelib/run.py5
5 files changed, 149 insertions, 63 deletions
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index e1c6fc7..a0f1869 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -2,6 +2,32 @@
IDLEfork NEWS
+++++++++++++
+What's New in IDLEfork 0.9 Alpha 3?
+===================================
+
+*Release date: xx-xxx-2003*
+
+- Exit IDLE cleanly even when doing subprocess I/O
+
+- Handle subprocess interrupt in Windows with an RPC message.
+
+- Calling Run will restart the subprocess even if user code is running.
+
+- Restart the subprocess if it terminates itself. (VPython programs do that)
+
+- Support subclassing of exceptions, including in the shell, by moving the
+ exception formatting to the subprocess.
+
+- Known issues:
+
+ + Can't kill/restart a tight loop in the Windows version: add
+ I/O to the loop or use the Task Manager to kill the subprocess.
+ + Typing two Control-C in close succession when the subprocess is busy can
+ cause IDLE to lose communication with the subprocess. Please type one
+ only and wait for the exception to complete.
+ + Printing under some versions of Linux may be problematic.
+
+
What's New in IDLEfork 0.9 Alpha 2?
===================================
@@ -105,15 +131,6 @@ What's New in IDLEfork 0.9 Alpha 2?
- Modified idle, idle.py, idle.pyw to improve exception handling.
-- Known issues:
-
- + Can't kill a tight loop in the Windows version: Insert a
- ``print "*",`` in an outer loop or use the Task Manager to kill.
- + Typing two Control-C in close succession when the subprocess is busy can
- cause IDLE to lose communication with the subprocess. Please type one
- only and wait for the exception to complete.
- + Printing under some versions of Linux may be problematic.
-
What's New in IDLEfork 0.9 Alpha 1?
===================================
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 98e0918..f329162 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -8,6 +8,7 @@ import getopt
import re
import socket
import time
+import threading
import traceback
import types
import exceptions
@@ -361,9 +362,23 @@ class ModifiedInterpreter(InteractiveInterpreter):
# close only the subprocess debugger
debug = self.getdebugger()
if debug:
- RemoteDebugger.close_subprocess_debugger(self.rpcclt)
- # kill subprocess, spawn a new one, accept connection
- self.rpcclt.close()
+ try:
+ RemoteDebugger.close_subprocess_debugger(self.rpcclt)
+ except:
+ pass
+ # Kill subprocess, spawn a new one, accept connection.
+ if hasattr(os, 'kill'):
+ # We can interrupt any loop if we can use SIGINT. This doesn't
+ # work in Windows, currently we can only interrupt loops doing I/O.
+ self.__signal_interrupt()
+ # XXX KBK 13Feb03 Don't close the socket until the interrupt thread
+ # finishes.
+ self.tkconsole.executing = False
+ try:
+ self.rpcclt.close()
+ os.wait()
+ except:
+ pass
self.spawn_subprocess()
self.rpcclt.accept()
self.transfer_path()
@@ -374,12 +389,30 @@ class ModifiedInterpreter(InteractiveInterpreter):
console.write(halfbar + ' RESTART ' + halfbar)
console.text.mark_set("restart", "end-1c")
console.text.mark_gravity("restart", "left")
- # restart remote debugger
+ # restart subprocess debugger
if debug:
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
# reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints()
+ def __signal_interrupt(self):
+ try:
+ from signal import SIGINT
+ except ImportError:
+ SIGINT = 2
+ os.kill(self.rpcpid, SIGINT)
+
+ def __request_interrupt(self):
+ self.rpcclt.asynccall("exec", "interrupt_the_server", (), {})
+
+ def interrupt_subprocess(self):
+ if hasattr(os, "kill"):
+ self.__signal_interrupt()
+ else:
+ # Windows has no os.kill(), use an RPC message.
+ # This is async, must be done in a thread.
+ threading.Thread(target=self.__request_interrupt).start()
+
def transfer_path(self):
self.runcommand("""if 1:
import sys as _sys
@@ -393,7 +426,20 @@ class ModifiedInterpreter(InteractiveInterpreter):
clt = self.rpcclt
if clt is None:
return
- response = clt.pollresponse(self.active_seq)
+ try:
+ response = clt.pollresponse(self.active_seq)
+ except (EOFError, IOError):
+ # lost connection: subprocess terminated itself, restart
+ if self.tkconsole.closing:
+ return
+ response = None
+ try:
+ # stake any zombie before restarting
+ os.wait()
+ except (AttributeError, OSError):
+ pass
+ self.restart_subprocess()
+ self.tkconsole.endexecuting()
# Reschedule myself in 50 ms
self.tkconsole.text.after(50, self.poll_subprocess)
if response:
@@ -571,8 +617,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
def runcode(self, code):
"Override base class method"
if self.tkconsole.executing:
- self.display_executing_dialog()
- return
+ self.interp.restart_subprocess()
self.checklinecache()
if self.save_warnings_filters is not None:
warnings.filters[:] = self.save_warnings_filters
@@ -670,10 +715,11 @@ class PyShell(OutputWindow):
if use_subprocess:
self.interp.start_subprocess()
- reading = 0
- executing = 0
- canceled = 0
- endoffile = 0
+ reading = False
+ executing = False
+ canceled = False
+ endoffile = False
+ closing = False
def toggle_debugger(self, event=None):
if self.executing:
@@ -748,17 +794,17 @@ class PyShell(OutputWindow):
def close(self):
"Extend EditorWindow.close()"
if self.executing:
- # XXX Need to ask a question here
- if not tkMessageBox.askokcancel(
+ response = tkMessageBox.askokcancel(
"Kill?",
- "The program is still running; do you want to kill it?",
+ "The program is still running!\n Do you want to kill it?",
default="ok",
- master=self.text):
+ master=self.text)
+ if response == False:
return "cancel"
- self.canceled = 1
- if self.reading:
- self.top.quit()
- return "cancel"
+ # interrupt the subprocess
+ self.closing = True
+ self.cancel_callback()
+ self.endexecuting()
return EditorWindow.close(self)
def _close(self):
@@ -819,7 +865,7 @@ class PyShell(OutputWindow):
def isatty(self):
return True
- def cancel_callback(self, event):
+ def cancel_callback(self, event=None):
try:
if self.text.compare("sel.first", "!=", "sel.last"):
return # Active selection -- always use default binding
@@ -831,18 +877,11 @@ class PyShell(OutputWindow):
self.showprompt()
return "break"
self.endoffile = 0
+ self.canceled = 1
if self.reading:
- self.canceled = 1
self.top.quit()
- elif (self.executing and self.interp.rpcclt and
- self.interp.rpcpid and hasattr(os, "kill")):
- try:
- from signal import SIGINT
- except ImportError:
- SIGINT = 2
- os.kill(self.interp.rpcpid, SIGINT)
- else:
- self.canceled = 1
+ elif (self.executing and self.interp.rpcclt):
+ self.interp.interrupt_subprocess()
return "break"
def eof_callback(self, event):
@@ -1020,12 +1059,14 @@ class PyShell(OutputWindow):
sys.stdout.softspace = 0
def write(self, s, tags=()):
- self.text.mark_gravity("iomark", "right")
- OutputWindow.write(self, s, tags, "iomark")
- self.text.mark_gravity("iomark", "left")
+ try:
+ self.text.mark_gravity("iomark", "right")
+ OutputWindow.write(self, s, tags, "iomark")
+ self.text.mark_gravity("iomark", "left")
+ except:
+ pass
if self.canceled:
self.canceled = 0
- raise KeyboardInterrupt
class PseudoFile:
diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py
index 0f44832..9604cb8 100644
--- a/Lib/idlelib/ScriptBinding.py
+++ b/Lib/idlelib/ScriptBinding.py
@@ -124,9 +124,6 @@ class ScriptBinding:
flist = self.editwin.flist
shell = flist.open_shell()
interp = shell.interp
- if interp.tkconsole.executing:
- interp.display_executing_dialog()
- return
interp.restart_subprocess()
# XXX Too often this discards arguments the user just set...
interp.runcommand("""if 1:
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
index b50643a..c79f4fe 100644
--- a/Lib/idlelib/rpc.py
+++ b/Lib/idlelib/rpc.py
@@ -86,6 +86,16 @@ class RPCServer(SocketServer.TCPServer):
"Override TCPServer method, return already connected socket"
return self.socket, self.server_address
+ def handle_error(self, request, client_address):
+ """Override TCPServer method, no error message if exiting"""
+ try:
+ raise
+ except SystemExit:
+ raise
+ else:
+ TCPServer.handle_error(request, client_address)
+
+
objecttable = {}
class SocketIO:
@@ -100,9 +110,10 @@ class SocketIO:
if objtable is None:
objtable = objecttable
self.objtable = objtable
- self.statelock = threading.Lock()
+ self.cvar = threading.Condition()
self.responses = {}
self.cvars = {}
+ self.interrupted = False
def close(self):
sock = self.sock
@@ -153,13 +164,16 @@ class SocketIO:
if isinstance(ret, RemoteObject):
ret = remoteref(ret)
return ("OK", ret)
+ except SystemExit:
+ raise
except:
self.debug("localcall:EXCEPTION")
+ if self.debugging: traceback.print_exc(file=sys.__stderr__)
efile = sys.stderr
typ, val, tb = info = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = info
tbe = traceback.extract_tb(tb)
- print >>efile, 'Traceback (most recent call last):'
+ print >>efile, '\nTraceback (most recent call last):'
exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
self.cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile)
@@ -186,9 +200,9 @@ class SocketIO:
break
del tb[-1]
if len(tb) == 0:
- # error was in RPC internals, don't prune!
+ # exception was in RPC internals, don't prune!
tb[:] = orig_tb[:]
- print>>sys.stderr, "** RPC Internal Error: ", tb
+ print>>sys.stderr, "** IDLE RPC Internal Exception: "
for i in range(len(tb)):
fn, ln, nm, line = tb[i]
if nm == '?':
@@ -199,7 +213,12 @@ class SocketIO:
tb[i] = fn, ln, nm, line
def remotecall(self, oid, methodname, args, kwargs):
- self.debug("calling asynccall via remotecall")
+ self.debug("remotecall:asynccall: ", oid, methodname)
+ # XXX KBK 06Feb03 self.interrupted logic may not be necessary if
+ # subprocess is threaded.
+ if self.interrupted:
+ self.interrupted = False
+ raise KeyboardInterrupt
seq = self.asynccall(oid, methodname, args, kwargs)
return self.asyncreturn(seq)
@@ -221,7 +240,8 @@ class SocketIO:
if how == "OK":
return what
if how == "EXCEPTION":
- raise Exception, "RPC SocketIO.decoderesponse exception"
+ self.debug("decoderesponse: EXCEPTION")
+ return None
if how == "ERROR":
self.debug("decoderesponse: Internal ERROR:", what)
raise RuntimeError, what
@@ -266,16 +286,15 @@ class SocketIO:
return response
else:
# Auxiliary thread: wait for notification from main thread
- cvar = threading.Condition(self.statelock)
- self.statelock.acquire()
- self.cvars[myseq] = cvar
+ self.cvar.acquire()
+ self.cvars[myseq] = self.cvar
while not self.responses.has_key(myseq):
- cvar.wait()
+ self.cvar.wait()
response = self.responses[myseq]
del self.responses[myseq]
del self.cvars[myseq]
- self.statelock.release()
- return response # might be None
+ self.cvar.release()
+ return response
def newseq(self):
self.nextseq = seq = self.nextseq + 2
@@ -290,8 +309,13 @@ class SocketIO:
raise
s = struct.pack("<i", len(s)) + s
while len(s) > 0:
- n = self.sock.send(s)
- s = s[n:]
+ try:
+ n = self.sock.send(s)
+ except AttributeError:
+ # socket was closed
+ raise IOError
+ else:
+ s = s[n:]
def ioready(self, wait=0.0):
r, w, x = select.select([self.sock.fileno()], [], [], wait)
@@ -374,12 +398,14 @@ class SocketIO:
elif seq == myseq:
return resq
else:
- self.statelock.acquire()
- self.responses[seq] = resq
+ self.cvar.acquire()
cv = self.cvars.get(seq)
+ # response involving unknown sequence number is discarded,
+ # probably intended for prior incarnation
if cv is not None:
+ self.responses[seq] = resq
cv.notify()
- self.statelock.release()
+ self.cvar.release()
continue
#----------------- end class SocketIO --------------------
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index f394bac..06fc049 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -71,6 +71,11 @@ class Executive:
def runcode(self, code):
exec code in self.locals
+ def interrupt_the_server(self):
+ # XXX KBK 05Feb03 Windows requires this be done with messages and
+ # threads....
+ self.rpchandler.interrupted = True
+
def start_the_debugger(self, gui_adap_oid):
return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)