[Idle-dev] CVS: idle NEWS.txt,1.10,1.11 PyShell.py,1.55,1.56 ScriptBinding.py,1.17,1.18 rpc.py,1.15,1.16 run.py,1.9,1.10
Kurt B. Kaiser
kbk@users.sourceforge.net
Mon, 17 Feb 2003 10:57:19 -0800
Update of /cvsroot/idlefork/idle
In directory sc8-pr-cvs1:/tmp/cvs-serv20344
Modified Files:
NEWS.txt PyShell.py ScriptBinding.py rpc.py run.py
Log Message:
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().
Index: NEWS.txt
===================================================================
RCS file: /cvsroot/idlefork/idle/NEWS.txt,v
retrieving revision 1.10
retrieving revision 1.11
diff -C2 -r1.10 -r1.11
*** NEWS.txt 27 Jan 2003 02:40:20 -0000 1.10
--- NEWS.txt 17 Feb 2003 18:57:16 -0000 1.11
***************
*** 3,6 ****
--- 3,32 ----
+++++++++++++
+ 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,117 ****
- 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.
--- 131,134 ----
Index: PyShell.py
===================================================================
RCS file: /cvsroot/idlefork/idle/PyShell.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -C2 -r1.55 -r1.56
*** PyShell.py 31 Jan 2003 05:06:43 -0000 1.55
--- PyShell.py 17 Feb 2003 18:57:16 -0000 1.56
***************
*** 9,12 ****
--- 9,13 ----
import socket
import time
+ import threading
import traceback
import types
***************
*** 362,368 ****
debug = self.getdebugger()
if debug:
! RemoteDebugger.close_subprocess_debugger(self.rpcclt)
! # kill subprocess, spawn a new one, accept connection
! self.rpcclt.close()
self.spawn_subprocess()
self.rpcclt.accept()
--- 363,383 ----
debug = self.getdebugger()
if debug:
! 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()
***************
*** 375,379 ****
console.text.mark_set("restart", "end-1c")
console.text.mark_gravity("restart", "left")
! # restart remote debugger
if debug:
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
--- 390,394 ----
console.text.mark_set("restart", "end-1c")
console.text.mark_gravity("restart", "left")
! # restart subprocess debugger
if debug:
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
***************
*** 381,384 ****
--- 396,417 ----
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:
***************
*** 394,398 ****
if clt is None:
return
! response = clt.pollresponse(self.active_seq)
# Reschedule myself in 50 ms
self.tkconsole.text.after(50, self.poll_subprocess)
--- 427,444 ----
if clt is None:
return
! 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)
***************
*** 572,577 ****
"Override base class method"
if self.tkconsole.executing:
! self.display_executing_dialog()
! return
self.checklinecache()
if self.save_warnings_filters is not None:
--- 618,622 ----
"Override base class method"
if self.tkconsole.executing:
! self.interp.restart_subprocess()
self.checklinecache()
if self.save_warnings_filters is not None:
***************
*** 671,678 ****
self.interp.start_subprocess()
! reading = 0
! executing = 0
! canceled = 0
! endoffile = 0
def toggle_debugger(self, event=None):
--- 716,724 ----
self.interp.start_subprocess()
! reading = False
! executing = False
! canceled = False
! endoffile = False
! closing = False
def toggle_debugger(self, event=None):
***************
*** 749,763 ****
"Extend EditorWindow.close()"
if self.executing:
! # XXX Need to ask a question here
! if not tkMessageBox.askokcancel(
"Kill?",
! "The program is still running; do you want to kill it?",
default="ok",
! master=self.text):
return "cancel"
! self.canceled = 1
! if self.reading:
! self.top.quit()
! return "cancel"
return EditorWindow.close(self)
--- 795,809 ----
"Extend EditorWindow.close()"
if self.executing:
! response = tkMessageBox.askokcancel(
"Kill?",
! "The program is still running!\n Do you want to kill it?",
default="ok",
! master=self.text)
! if response == False:
return "cancel"
! # interrupt the subprocess
! self.closing = True
! self.cancel_callback()
! self.endexecuting()
return EditorWindow.close(self)
***************
*** 820,824 ****
return True
! def cancel_callback(self, event):
try:
if self.text.compare("sel.first", "!=", "sel.last"):
--- 866,870 ----
return True
! def cancel_callback(self, event=None):
try:
if self.text.compare("sel.first", "!=", "sel.last"):
***************
*** 832,847 ****
return "break"
self.endoffile = 0
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
return "break"
--- 878,886 ----
return "break"
self.endoffile = 0
+ self.canceled = 1
if self.reading:
self.top.quit()
! elif (self.executing and self.interp.rpcclt):
! self.interp.interrupt_subprocess()
return "break"
***************
*** 1021,1030 ****
def write(self, s, tags=()):
! self.text.mark_gravity("iomark", "right")
! OutputWindow.write(self, s, tags, "iomark")
! self.text.mark_gravity("iomark", "left")
if self.canceled:
self.canceled = 0
- raise KeyboardInterrupt
class PseudoFile:
--- 1060,1071 ----
def write(self, s, tags=()):
! 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
class PseudoFile:
Index: ScriptBinding.py
===================================================================
RCS file: /cvsroot/idlefork/idle/ScriptBinding.py,v
retrieving revision 1.17
retrieving revision 1.18
diff -C2 -r1.17 -r1.18
*** ScriptBinding.py 26 Jan 2003 04:17:16 -0000 1.17
--- ScriptBinding.py 17 Feb 2003 18:57:16 -0000 1.18
***************
*** 125,131 ****
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...
--- 125,128 ----
Index: rpc.py
===================================================================
RCS file: /cvsroot/idlefork/idle/rpc.py,v
retrieving revision 1.15
retrieving revision 1.16
diff -C2 -r1.15 -r1.16
*** rpc.py 31 Jan 2003 05:06:43 -0000 1.15
--- rpc.py 17 Feb 2003 18:57:16 -0000 1.16
***************
*** 87,90 ****
--- 87,100 ----
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 = {}
***************
*** 101,107 ****
objtable = objecttable
self.objtable = objtable
! self.statelock = threading.Lock()
self.responses = {}
self.cvars = {}
def close(self):
--- 111,118 ----
objtable = objecttable
self.objtable = objtable
! self.cvar = threading.Condition()
self.responses = {}
self.cvars = {}
+ self.interrupted = False
def close(self):
***************
*** 154,164 ****
ret = remoteref(ret)
return ("OK", ret)
except:
self.debug("localcall:EXCEPTION")
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):'
exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
self.cleanup_traceback(tbe, exclude)
--- 165,178 ----
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, '\nTraceback (most recent call last):'
exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
self.cleanup_traceback(tbe, exclude)
***************
*** 187,193 ****
del tb[-1]
if len(tb) == 0:
! # error was in RPC internals, don't prune!
tb[:] = orig_tb[:]
! print>>sys.stderr, "** RPC Internal Error: ", tb
for i in range(len(tb)):
fn, ln, nm, line = tb[i]
--- 201,207 ----
del tb[-1]
if len(tb) == 0:
! # exception was in RPC internals, don't prune!
tb[:] = orig_tb[:]
! print>>sys.stderr, "** IDLE RPC Internal Exception: "
for i in range(len(tb)):
fn, ln, nm, line = tb[i]
***************
*** 200,204 ****
def remotecall(self, oid, methodname, args, kwargs):
! self.debug("calling asynccall via remotecall")
seq = self.asynccall(oid, methodname, args, kwargs)
return self.asyncreturn(seq)
--- 214,223 ----
def remotecall(self, oid, methodname, args, kwargs):
! 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)
***************
*** 222,226 ****
return what
if how == "EXCEPTION":
! raise Exception, "RPC SocketIO.decoderesponse exception"
if how == "ERROR":
self.debug("decoderesponse: Internal ERROR:", what)
--- 241,246 ----
return what
if how == "EXCEPTION":
! self.debug("decoderesponse: EXCEPTION")
! return None
if how == "ERROR":
self.debug("decoderesponse: Internal ERROR:", what)
***************
*** 267,280 ****
else:
# Auxiliary thread: wait for notification from main thread
! cvar = threading.Condition(self.statelock)
! self.statelock.acquire()
! self.cvars[myseq] = cvar
while not self.responses.has_key(myseq):
! cvar.wait()
response = self.responses[myseq]
del self.responses[myseq]
del self.cvars[myseq]
! self.statelock.release()
! return response # might be None
def newseq(self):
--- 287,299 ----
else:
# Auxiliary thread: wait for notification from main thread
! self.cvar.acquire()
! self.cvars[myseq] = self.cvar
while not self.responses.has_key(myseq):
! self.cvar.wait()
response = self.responses[myseq]
del self.responses[myseq]
del self.cvars[myseq]
! self.cvar.release()
! return response
def newseq(self):
***************
*** 291,296 ****
s = struct.pack("<i", len(s)) + s
while len(s) > 0:
! n = self.sock.send(s)
! s = s[n:]
def ioready(self, wait=0.0):
--- 310,320 ----
s = struct.pack("<i", len(s)) + s
while len(s) > 0:
! try:
! n = self.sock.send(s)
! except AttributeError:
! # socket was closed
! raise IOError
! else:
! s = s[n:]
def ioready(self, wait=0.0):
***************
*** 375,384 ****
return resq
else:
! self.statelock.acquire()
! self.responses[seq] = resq
cv = self.cvars.get(seq)
if cv is not None:
cv.notify()
! self.statelock.release()
continue
--- 399,410 ----
return resq
else:
! 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.cvar.release()
continue
Index: run.py
===================================================================
RCS file: /cvsroot/idlefork/idle/run.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -C2 -r1.9 -r1.10
*** run.py 20 Dec 2002 04:24:43 -0000 1.9
--- run.py 17 Feb 2003 18:57:16 -0000 1.10
***************
*** 72,75 ****
--- 72,80 ----
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)