Commit a00050f2 authored by Kurt B. Kaiser's avatar Kurt B. Kaiser

1. Implement processing of user code in subprocess MainThread. Pass loop

   is now interruptable on Windows.
2. Tweak signal.signal() wait parameters as called by various methods
   to improve I/O response, especially on Windows.
3. Debugger is disabled at this check-in pending further development.

M NEWS.txt
M PyShell.py
M rpc.py
M run.py
parent c4607dad
......@@ -5,7 +5,13 @@ IDLEfork NEWS
What's New in IDLEfork 0.9b1?
===================================
*Release date: 25-Apr-2003*
*Release date: XX-XXX-2003*
- Improved I/O response by tweaking the wait parameter in various
calls to signal.signal().
- Implemented a threaded subprocess which allows interrupting a pass
loop in user code using the 'interrupt' extension.
- Implemented the 'interrupt' extension module, which allows a subthread
to raise a KeyboardInterrupt in the main thread.
......@@ -36,11 +42,10 @@ What's New in IDLEfork 0.9b1?
- 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.
only and wait for the exception to complete. If you do manage to
interrupt the interrupt, simply restart the shell.
+ Printing under some versions of Linux may be problematic.
......
......@@ -35,6 +35,11 @@ import RemoteDebugger
IDENTCHARS = string.ascii_letters + string.digits + "_"
try:
from signal import SIGTERM
except ImportError:
SIGTERM = 15
# Change warnings module to write to sys.__stderr__
try:
import warnings
......@@ -367,13 +372,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
except:
pass
# Kill subprocess, spawn a new one, accept connection.
try:
self.interrupt_subprocess()
self.shutdown_subprocess()
self.rpcclt.close()
os.wait()
except:
pass
self.unix_terminate()
self.tkconsole.executing = False
self.spawn_subprocess()
self.rpcclt.accept()
......@@ -391,42 +391,31 @@ class ModifiedInterpreter(InteractiveInterpreter):
# reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints()
def __signal_interrupt(self):
try:
from signal import SIGINT
except ImportError:
SIGINT = 2
try:
os.kill(self.rpcpid, SIGINT)
except OSError: # subprocess may have already exited
pass
def __request_interrupt(self):
try:
self.rpcclt.asynccall("exec", "interrupt_the_server", (), {})
except:
pass
self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
def interrupt_subprocess(self):
# XXX KBK 22Mar03 Use interrupt message on all platforms for now.
# XXX if hasattr(os, "kill"):
if False:
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 __request_shutdown(self):
try:
self.rpcclt.asynccall("exec", "shutdown_the_server", (), {})
except:
pass
def kill_subprocess(self):
self.rpcclt.close()
self.unix_terminate()
self.tkconsole.executing = False
self.rpcclt = None
def shutdown_subprocess(self):
t = threading.Thread(target=self.__request_shutdown)
t.start()
t.join()
def unix_terminate(self):
"UNIX: make sure subprocess is terminated and collect status"
if hasattr(os, 'kill'):
try:
os.kill(self.rpcpid, SIGTERM)
except OSError:
# process already terminated:
return
else:
try:
os.waitpid(self.rpcpid, 0)
except OSError:
return
def transfer_path(self):
self.runcommand("""if 1:
......@@ -445,21 +434,15 @@ class ModifiedInterpreter(InteractiveInterpreter):
if clt is None:
return
try:
response = clt.pollresponse(self.active_seq)
except (EOFError, IOError):
# lost connection: subprocess terminated itself, restart
response = clt.pollresponse(self.active_seq, wait=0.05)
except (EOFError, IOError, KeyboardInterrupt):
# lost connection or subprocess terminated itself, restart
# [the KBI is from rpc.SocketIO.handle_EOF()]
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:
self.tkconsole.resetoutput()
self.active_seq = None
......@@ -477,13 +460,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
print >>console, errmsg, what
# we received a response to the currently active seq number:
self.tkconsole.endexecuting()
def kill_subprocess(self):
clt = self.rpcclt
if clt is not None:
self.shutdown_subprocess()
clt.close()
self.rpcclt = None
# Reschedule myself in 50 ms
self.tkconsole.text.after(50, self.poll_subprocess)
debugger = None
......@@ -495,7 +473,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
def remote_stack_viewer(self):
import RemoteObjectBrowser
oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {})
oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
if oid is None:
self.tkconsole.root.bell()
return
......@@ -628,7 +606,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.display_executing_dialog()
return 0
if self.rpcclt:
self.rpcclt.remotecall("exec", "runcode", (code,), {})
self.rpcclt.remotequeue("exec", "runcode", (code,), {})
else:
exec code in self.locals
return 1
......@@ -645,7 +623,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.tkconsole.beginexecuting()
try:
if not debugger and self.rpcclt is not None:
self.active_seq = self.rpcclt.asynccall("exec", "runcode",
self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
(code,), {})
elif debugger:
debugger.run(code, self.locals)
......@@ -712,7 +690,7 @@ class PyShell(OutputWindow):
text.bind("<<beginning-of-line>>", self.home_callback)
text.bind("<<end-of-file>>", self.eof_callback)
text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
text.bind("<<toggle-debugger>>", self.toggle_debugger)
##text.bind("<<toggle-debugger>>", self.toggle_debugger)
text.bind("<<open-python-shell>>", self.flist.open_shell)
text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
text.bind("<<view-restart>>", self.view_restart_mark)
......@@ -799,13 +777,9 @@ class PyShell(OutputWindow):
"Helper for ModifiedInterpreter"
self.resetoutput()
self.executing = 1
##self._cancel_check = self.cancel_check
##sys.settrace(self._cancel_check)
def endexecuting(self):
"Helper for ModifiedInterpreter"
##sys.settrace(None)
##self._cancel_check = None
self.executing = 0
self.canceled = 0
self.showprompt()
......@@ -822,7 +796,6 @@ class PyShell(OutputWindow):
return "cancel"
# interrupt the subprocess
self.closing = True
self.cancel_callback()
self.endexecuting()
return EditorWindow.close(self)
......@@ -1017,23 +990,6 @@ class PyShell(OutputWindow):
line = line[:i]
more = self.interp.runsource(line)
def cancel_check(self, frame, what, args,
dooneevent=tkinter.dooneevent,
dontwait=tkinter.DONT_WAIT):
# Hack -- use the debugger hooks to be able to handle events
# and interrupt execution at any time.
# This slows execution down quite a bit, so you may want to
# disable this (by not calling settrace() in beginexecuting() and
# endexecuting() for full-bore (uninterruptable) speed.)
# XXX This should become a user option.
if self.canceled:
return
dooneevent(dontwait)
if self.canceled:
self.canceled = 0
raise KeyboardInterrupt
return self._cancel_check
def open_stack_viewer(self, event=None):
if self.interp.rpcclt:
return self.interp.remote_stack_viewer()
......
This diff is collapsed.
import sys
import os
import time
import socket
import traceback
......@@ -20,12 +21,8 @@ import __main__
# the socket) and the main thread (which runs user code), plus global
# completion and exit flags:
server = None # RPCServer instance
queue = Queue.Queue(0)
execution_finished = False
exit_requested = False
def main():
"""Start the Python execution server in a subprocess
......@@ -44,8 +41,6 @@ def main():
register and unregister themselves.
"""
global queue, execution_finished, exit_requested
port = 8833
if sys.argv[1:]:
port = int(sys.argv[1])
......@@ -58,21 +53,23 @@ def main():
while 1:
try:
if exit_requested:
sys.exit()
# XXX KBK 22Mar03 eventually check queue here!
pass
os._exit(0)
try:
seq, request = rpc.request_queue.get(0)
except Queue.Empty:
time.sleep(0.05)
continue
method, args, kwargs = request
ret = method(*args, **kwargs)
rpc.response_queue.put((seq, ret))
except KeyboardInterrupt:
##execution_finished = True
continue
def manage_socket(address):
global server, exit_requested
for i in range(6):
time.sleep(i)
try:
server = rpc.RPCServer(address, MyHandler)
server = MyRPCServer(address, MyHandler)
break
except socket.error, err:
if i < 3:
......@@ -82,10 +79,41 @@ def manage_socket(address):
+ err[1] + ", retrying...."
else:
print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
global exit_requested
exit_requested = True
return
server.handle_request() # A single request only
class MyRPCServer(rpc.RPCServer):
def handle_error(self, request, client_address):
"""Override RPCServer method for IDLE
Interrupt the MainThread and exit server if link is dropped.
"""
try:
raise
except SystemExit:
raise
except EOFError:
global exit_requested
exit_requested = True
interrupt.interrupt_main()
except:
erf = sys.__stderr__
print>>erf, '\n' + '-'*40
print>>erf, 'Unhandled server exception!'
print>>erf, 'Thread: %s' % threading.currentThread().getName()
print>>erf, 'Client Address: ', client_address
print>>erf, 'Request: ', repr(request)
traceback.print_exc(file=erf)
print>>erf, '\n*** Unrecoverable, server exiting!'
print>>erf, '-'*40
os._exit(0)
class MyHandler(rpc.RPCHandler):
def handle(self):
......@@ -95,7 +123,20 @@ class MyHandler(rpc.RPCHandler):
sys.stdin = self.get_remote_proxy("stdin")
sys.stdout = self.get_remote_proxy("stdout")
sys.stderr = self.get_remote_proxy("stderr")
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.5)
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
def exithook(self):
"override SocketIO method - wait for MainThread to shut us down"
while 1: pass
def EOFhook(self):
"Override SocketIO method - terminate wait on callback and exit thread"
global exit_requested
exit_requested = True
def decode_interrupthook(self):
"interrupt awakened thread"
interrupt.interrupt_main()
class Executive:
......@@ -106,44 +147,30 @@ class Executive:
self.calltip = CallTips.CallTips()
def runcode(self, code):
global queue, execution_finished
execution_finished = False
queue.put(code)
# dequeue and run in subthread
self.runcode_from_queue()
while not execution_finished:
time.sleep(0.05)
def runcode_from_queue(self):
global queue, execution_finished
# poll until queue has code object, using threads, just block?
while True:
try:
code = queue.get(0)
break
except Queue.Empty:
time.sleep(0.05)
try:
exec code in self.locals
except:
try:
if exit_requested:
os._exit(0)
self.flush_stdout()
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")
exclude = ("run.py", "rpc.py", "threading.py",
"RemoteDebugger.py", "bdb.py")
self.cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile)
lines = traceback.format_exception_only(typ, val)
for line in lines:
print>>efile, line,
execution_finished = True
except:
sys.stderr = sys.__stderr__
raise
else:
self.flush_stdout()
execution_finished = True
def flush_stdout(self):
try:
......@@ -184,15 +211,8 @@ class Executive:
tb[i] = fn, ln, nm, line
def interrupt_the_server(self):
self.rpchandler.interrupted = True
##print>>sys.__stderr__, "** Interrupt main!"
interrupt.interrupt_main()
def shutdown_the_server(self):
global exit_requested
exit_requested = True
def start_the_debugger(self, gui_adap_oid):
return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment