Kaydet (Commit) a00050f2 authored tarafından 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
üst c4607dad
...@@ -5,7 +5,13 @@ IDLEfork NEWS ...@@ -5,7 +5,13 @@ IDLEfork NEWS
What's New in IDLEfork 0.9b1? 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 - Implemented the 'interrupt' extension module, which allows a subthread
to raise a KeyboardInterrupt in the main thread. to raise a KeyboardInterrupt in the main thread.
...@@ -36,11 +42,10 @@ What's New in IDLEfork 0.9b1? ...@@ -36,11 +42,10 @@ What's New in IDLEfork 0.9b1?
- Known issues: - 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 + Typing two Control-C in close succession when the subprocess is busy can
cause IDLE to lose communication with the subprocess. Please type one 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. + Printing under some versions of Linux may be problematic.
......
...@@ -35,6 +35,11 @@ import RemoteDebugger ...@@ -35,6 +35,11 @@ import RemoteDebugger
IDENTCHARS = string.ascii_letters + string.digits + "_" IDENTCHARS = string.ascii_letters + string.digits + "_"
try:
from signal import SIGTERM
except ImportError:
SIGTERM = 15
# Change warnings module to write to sys.__stderr__ # Change warnings module to write to sys.__stderr__
try: try:
import warnings import warnings
...@@ -367,13 +372,8 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -367,13 +372,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
except: except:
pass pass
# Kill subprocess, spawn a new one, accept connection. # Kill subprocess, spawn a new one, accept connection.
try:
self.interrupt_subprocess()
self.shutdown_subprocess()
self.rpcclt.close() self.rpcclt.close()
os.wait() self.unix_terminate()
except:
pass
self.tkconsole.executing = False self.tkconsole.executing = False
self.spawn_subprocess() self.spawn_subprocess()
self.rpcclt.accept() self.rpcclt.accept()
...@@ -391,42 +391,31 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -391,42 +391,31 @@ class ModifiedInterpreter(InteractiveInterpreter):
# reload remote debugger breakpoints for all PyShellEditWindows # reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints() 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): def __request_interrupt(self):
try: self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
self.rpcclt.asynccall("exec", "interrupt_the_server", (), {})
except:
pass
def interrupt_subprocess(self): 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() threading.Thread(target=self.__request_interrupt).start()
def __request_shutdown(self): def kill_subprocess(self):
try: self.rpcclt.close()
self.rpcclt.asynccall("exec", "shutdown_the_server", (), {}) self.unix_terminate()
except: self.tkconsole.executing = False
pass self.rpcclt = None
def shutdown_subprocess(self): def unix_terminate(self):
t = threading.Thread(target=self.__request_shutdown) "UNIX: make sure subprocess is terminated and collect status"
t.start() if hasattr(os, 'kill'):
t.join() 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): def transfer_path(self):
self.runcommand("""if 1: self.runcommand("""if 1:
...@@ -445,21 +434,15 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -445,21 +434,15 @@ class ModifiedInterpreter(InteractiveInterpreter):
if clt is None: if clt is None:
return return
try: try:
response = clt.pollresponse(self.active_seq) response = clt.pollresponse(self.active_seq, wait=0.05)
except (EOFError, IOError): except (EOFError, IOError, KeyboardInterrupt):
# lost connection: subprocess terminated itself, restart # lost connection or subprocess terminated itself, restart
# [the KBI is from rpc.SocketIO.handle_EOF()]
if self.tkconsole.closing: if self.tkconsole.closing:
return return
response = None response = None
try:
# stake any zombie before restarting
os.wait()
except (AttributeError, OSError):
pass
self.restart_subprocess() self.restart_subprocess()
self.tkconsole.endexecuting() self.tkconsole.endexecuting()
# Reschedule myself in 50 ms
self.tkconsole.text.after(50, self.poll_subprocess)
if response: if response:
self.tkconsole.resetoutput() self.tkconsole.resetoutput()
self.active_seq = None self.active_seq = None
...@@ -477,13 +460,8 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -477,13 +460,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
print >>console, errmsg, what print >>console, errmsg, what
# we received a response to the currently active seq number: # we received a response to the currently active seq number:
self.tkconsole.endexecuting() self.tkconsole.endexecuting()
# Reschedule myself in 50 ms
def kill_subprocess(self): self.tkconsole.text.after(50, self.poll_subprocess)
clt = self.rpcclt
if clt is not None:
self.shutdown_subprocess()
clt.close()
self.rpcclt = None
debugger = None debugger = None
...@@ -495,7 +473,7 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -495,7 +473,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
def remote_stack_viewer(self): def remote_stack_viewer(self):
import RemoteObjectBrowser import RemoteObjectBrowser
oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {}) oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
if oid is None: if oid is None:
self.tkconsole.root.bell() self.tkconsole.root.bell()
return return
...@@ -628,7 +606,7 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -628,7 +606,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.display_executing_dialog() self.display_executing_dialog()
return 0 return 0
if self.rpcclt: if self.rpcclt:
self.rpcclt.remotecall("exec", "runcode", (code,), {}) self.rpcclt.remotequeue("exec", "runcode", (code,), {})
else: else:
exec code in self.locals exec code in self.locals
return 1 return 1
...@@ -645,7 +623,7 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -645,7 +623,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.tkconsole.beginexecuting() self.tkconsole.beginexecuting()
try: try:
if not debugger and self.rpcclt is not None: 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,), {}) (code,), {})
elif debugger: elif debugger:
debugger.run(code, self.locals) debugger.run(code, self.locals)
...@@ -712,7 +690,7 @@ class PyShell(OutputWindow): ...@@ -712,7 +690,7 @@ class PyShell(OutputWindow):
text.bind("<<beginning-of-line>>", self.home_callback) text.bind("<<beginning-of-line>>", self.home_callback)
text.bind("<<end-of-file>>", self.eof_callback) text.bind("<<end-of-file>>", self.eof_callback)
text.bind("<<open-stack-viewer>>", self.open_stack_viewer) 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("<<open-python-shell>>", self.flist.open_shell)
text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer) text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
text.bind("<<view-restart>>", self.view_restart_mark) text.bind("<<view-restart>>", self.view_restart_mark)
...@@ -799,13 +777,9 @@ class PyShell(OutputWindow): ...@@ -799,13 +777,9 @@ class PyShell(OutputWindow):
"Helper for ModifiedInterpreter" "Helper for ModifiedInterpreter"
self.resetoutput() self.resetoutput()
self.executing = 1 self.executing = 1
##self._cancel_check = self.cancel_check
##sys.settrace(self._cancel_check)
def endexecuting(self): def endexecuting(self):
"Helper for ModifiedInterpreter" "Helper for ModifiedInterpreter"
##sys.settrace(None)
##self._cancel_check = None
self.executing = 0 self.executing = 0
self.canceled = 0 self.canceled = 0
self.showprompt() self.showprompt()
...@@ -822,7 +796,6 @@ class PyShell(OutputWindow): ...@@ -822,7 +796,6 @@ class PyShell(OutputWindow):
return "cancel" return "cancel"
# interrupt the subprocess # interrupt the subprocess
self.closing = True self.closing = True
self.cancel_callback()
self.endexecuting() self.endexecuting()
return EditorWindow.close(self) return EditorWindow.close(self)
...@@ -1017,23 +990,6 @@ class PyShell(OutputWindow): ...@@ -1017,23 +990,6 @@ class PyShell(OutputWindow):
line = line[:i] line = line[:i]
more = self.interp.runsource(line) 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): def open_stack_viewer(self, event=None):
if self.interp.rpcclt: if self.interp.rpcclt:
return self.interp.remote_stack_viewer() return self.interp.remote_stack_viewer()
......
...@@ -28,17 +28,21 @@ accomplished in Idle. ...@@ -28,17 +28,21 @@ accomplished in Idle.
""" """
import sys import sys
import os
import socket import socket
import select import select
import SocketServer import SocketServer
import struct import struct
import cPickle as pickle import cPickle as pickle
import threading import threading
import Queue
import traceback import traceback
import copy_reg import copy_reg
import types import types
import marshal import marshal
import interrupt
def unpickle_code(ms): def unpickle_code(ms):
co = marshal.loads(ms) co = marshal.loads(ms)
assert isinstance(co, types.CodeType) assert isinstance(co, types.CodeType)
...@@ -98,8 +102,6 @@ class RPCServer(SocketServer.TCPServer): ...@@ -98,8 +102,6 @@ class RPCServer(SocketServer.TCPServer):
raise raise
except SystemExit: except SystemExit:
raise raise
except EOFError:
pass
except: except:
erf = sys.__stderr__ erf = sys.__stderr__
print>>erf, '\n' + '-'*40 print>>erf, '\n' + '-'*40
...@@ -110,28 +112,29 @@ class RPCServer(SocketServer.TCPServer): ...@@ -110,28 +112,29 @@ class RPCServer(SocketServer.TCPServer):
traceback.print_exc(file=erf) traceback.print_exc(file=erf)
print>>erf, '\n*** Unrecoverable, server exiting!' print>>erf, '\n*** Unrecoverable, server exiting!'
print>>erf, '-'*40 print>>erf, '-'*40
import os
os._exit(0) os._exit(0)
#----------------- end class RPCServer --------------------
objecttable = {} objecttable = {}
request_queue = Queue.Queue(0)
response_queue = Queue.Queue(0)
class SocketIO: class SocketIO:
nextseq = 0 nextseq = 0
def __init__(self, sock, objtable=None, debugging=None): def __init__(self, sock, objtable=None, debugging=None):
self.mainthread = threading.currentThread() self.sockthread = threading.currentThread()
if debugging is not None: if debugging is not None:
self.debugging = debugging self.debugging = debugging
self.sock = sock self.sock = sock
if objtable is None: if objtable is None:
objtable = objecttable objtable = objecttable
self.objtable = objtable self.objtable = objtable
self.cvar = threading.Condition()
self.responses = {} self.responses = {}
self.cvars = {} self.cvars = {}
self.interrupted = False
def close(self): def close(self):
sock = self.sock sock = self.sock
...@@ -139,6 +142,10 @@ class SocketIO: ...@@ -139,6 +142,10 @@ class SocketIO:
if sock is not None: if sock is not None:
sock.close() sock.close()
def exithook(self):
"override for specific exit action"
os._exit()
def debug(self, *args): def debug(self, *args):
if not self.debugging: if not self.debugging:
return return
...@@ -156,13 +163,12 @@ class SocketIO: ...@@ -156,13 +163,12 @@ class SocketIO:
except KeyError: except KeyError:
pass pass
def localcall(self, request): def localcall(self, seq, request):
self.debug("localcall:", request) self.debug("localcall:", request)
try: try:
how, (oid, methodname, args, kwargs) = request how, (oid, methodname, args, kwargs) = request
except TypeError: except TypeError:
return ("ERROR", "Bad request format") return ("ERROR", "Bad request format")
assert how == "call"
if not self.objtable.has_key(oid): if not self.objtable.has_key(oid):
return ("ERROR", "Unknown object id: %s" % `oid`) return ("ERROR", "Unknown object id: %s" % `oid`)
obj = self.objtable[oid] obj = self.objtable[oid]
...@@ -178,14 +184,20 @@ class SocketIO: ...@@ -178,14 +184,20 @@ class SocketIO:
return ("ERROR", "Unsupported method name: %s" % `methodname`) return ("ERROR", "Unsupported method name: %s" % `methodname`)
method = getattr(obj, methodname) method = getattr(obj, methodname)
try: try:
if how == 'CALL':
ret = method(*args, **kwargs) ret = method(*args, **kwargs)
if isinstance(ret, RemoteObject): if isinstance(ret, RemoteObject):
ret = remoteref(ret) ret = remoteref(ret)
return ("OK", ret) return ("OK", ret)
elif how == 'QUEUE':
request_queue.put((seq, (method, args, kwargs)))
return("QUEUED", None)
else:
return ("ERROR", "Unsupported message type: %s" % how)
except SystemExit: except SystemExit:
raise raise
except socket.error: except socket.error:
pass raise
except: except:
self.debug("localcall:EXCEPTION") self.debug("localcall:EXCEPTION")
traceback.print_exc(file=sys.__stderr__) traceback.print_exc(file=sys.__stderr__)
...@@ -193,24 +205,37 @@ class SocketIO: ...@@ -193,24 +205,37 @@ class SocketIO:
def remotecall(self, oid, methodname, args, kwargs): def remotecall(self, oid, methodname, args, kwargs):
self.debug("remotecall:asynccall: ", oid, methodname) 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) seq = self.asynccall(oid, methodname, args, kwargs)
return self.asyncreturn(seq) return self.asyncreturn(seq)
def remotequeue(self, oid, methodname, args, kwargs):
self.debug("remotequeue:asyncqueue: ", oid, methodname)
seq = self.asyncqueue(oid, methodname, args, kwargs)
return self.asyncreturn(seq)
def asynccall(self, oid, methodname, args, kwargs): def asynccall(self, oid, methodname, args, kwargs):
request = ("call", (oid, methodname, args, kwargs)) request = ("CALL", (oid, methodname, args, kwargs))
seq = self.newseq() seq = self.newseq()
if threading.currentThread() != self.sockthread:
cvar = threading.Condition()
self.cvars[seq] = cvar
self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs) self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
self.putmessage((seq, request)) self.putmessage((seq, request))
return seq return seq
def asyncqueue(self, oid, methodname, args, kwargs):
request = ("QUEUE", (oid, methodname, args, kwargs))
seq = self.newseq()
if threading.currentThread() != self.sockthread:
cvar = threading.Condition()
self.cvars[seq] = cvar
self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs)
self.putmessage((seq, request))
return seq
def asyncreturn(self, seq): def asyncreturn(self, seq):
self.debug("asyncreturn:%d:call getresponse(): " % seq) self.debug("asyncreturn:%d:call getresponse(): " % seq)
response = self.getresponse(seq, wait=None) response = self.getresponse(seq, wait=0.05)
self.debug(("asyncreturn:%d:response: " % seq), response) self.debug(("asyncreturn:%d:response: " % seq), response)
return self.decoderesponse(response) return self.decoderesponse(response)
...@@ -218,25 +243,36 @@ class SocketIO: ...@@ -218,25 +243,36 @@ class SocketIO:
how, what = response how, what = response
if how == "OK": if how == "OK":
return what return what
if how == "QUEUED":
return None
if how == "EXCEPTION": if how == "EXCEPTION":
self.debug("decoderesponse: EXCEPTION") self.debug("decoderesponse: EXCEPTION")
return None return None
if how == "EOF":
self.debug("decoderesponse: EOF")
self.decode_interrupthook()
return None
if how == "ERROR": if how == "ERROR":
self.debug("decoderesponse: Internal ERROR:", what) self.debug("decoderesponse: Internal ERROR:", what)
raise RuntimeError, what raise RuntimeError, what
raise SystemError, (how, what) raise SystemError, (how, what)
def decode_interrupthook(self):
""
raise EOFError
def mainloop(self): def mainloop(self):
"""Listen on socket until I/O not ready or EOF """Listen on socket until I/O not ready or EOF
Main thread pollresponse() will loop looking for seq number None, which pollresponse() will loop looking for seq number None, which
never comes, and exit on EOFError. never comes, and exit on EOFError.
""" """
try: try:
self.getresponse(myseq=None, wait=None) self.getresponse(myseq=None, wait=0.05)
except EOFError: except EOFError:
pass self.debug("mainloop:return")
return
def getresponse(self, myseq, wait): def getresponse(self, myseq, wait):
response = self._getresponse(myseq, wait) response = self._getresponse(myseq, wait)
...@@ -256,23 +292,24 @@ class SocketIO: ...@@ -256,23 +292,24 @@ class SocketIO:
def _getresponse(self, myseq, wait): def _getresponse(self, myseq, wait):
self.debug("_getresponse:myseq:", myseq) self.debug("_getresponse:myseq:", myseq)
if threading.currentThread() is self.mainthread: if threading.currentThread() is self.sockthread:
# Main thread: does all reading of requests or responses # this thread does all reading of requests or responses
# Loop here, blocking each time until socket is ready.
while 1: while 1:
response = self.pollresponse(myseq, wait) response = self.pollresponse(myseq, wait)
if response is not None: if response is not None:
return response return response
else: else:
# Auxiliary thread: wait for notification from main thread # wait for notification from socket handling thread
self.cvar.acquire() cvar = self.cvars[myseq]
self.cvars[myseq] = self.cvar cvar.acquire()
while not self.responses.has_key(myseq): while not self.responses.has_key(myseq):
self.cvar.wait() cvar.wait()
response = self.responses[myseq] response = self.responses[myseq]
self.debug("_getresponse:%s: thread woke up: response: %s" %
(myseq, response))
del self.responses[myseq] del self.responses[myseq]
del self.cvars[myseq] del self.cvars[myseq]
self.cvar.release() cvar.release()
return response return response
def newseq(self): def newseq(self):
...@@ -283,7 +320,7 @@ class SocketIO: ...@@ -283,7 +320,7 @@ class SocketIO:
self.debug("putmessage:%d:" % message[0]) self.debug("putmessage:%d:" % message[0])
try: try:
s = pickle.dumps(message) s = pickle.dumps(message)
except: except pickle.UnpicklingError:
print >>sys.__stderr__, "Cannot pickle:", `message` print >>sys.__stderr__, "Cannot pickle:", `message`
raise raise
s = struct.pack("<i", len(s)) + s s = struct.pack("<i", len(s)) + s
...@@ -293,10 +330,13 @@ class SocketIO: ...@@ -293,10 +330,13 @@ class SocketIO:
except AttributeError: except AttributeError:
# socket was closed # socket was closed
raise IOError raise IOError
except socket.error:
self.debug("putmessage:socketerror:pid:%s" % os.getpid())
os._exit(0)
else: else:
s = s[n:] s = s[n:]
def ioready(self, wait=0.0): def ioready(self, wait):
r, w, x = select.select([self.sock.fileno()], [], [], wait) r, w, x = select.select([self.sock.fileno()], [], [], wait)
return len(r) return len(r)
...@@ -304,7 +344,7 @@ class SocketIO: ...@@ -304,7 +344,7 @@ class SocketIO:
bufneed = 4 bufneed = 4
bufstate = 0 # meaning: 0 => reading count; 1 => reading data bufstate = 0 # meaning: 0 => reading count; 1 => reading data
def pollpacket(self, wait=0.0): def pollpacket(self, wait):
self._stage0() self._stage0()
if len(self.buffer) < self.bufneed: if len(self.buffer) < self.bufneed:
if not self.ioready(wait): if not self.ioready(wait):
...@@ -334,7 +374,7 @@ class SocketIO: ...@@ -334,7 +374,7 @@ class SocketIO:
self.bufstate = 0 self.bufstate = 0
return packet return packet
def pollmessage(self, wait=0.0): def pollmessage(self, wait):
packet = self.pollpacket(wait) packet = self.pollpacket(wait)
if packet is None: if packet is None:
return None return None
...@@ -348,45 +388,97 @@ class SocketIO: ...@@ -348,45 +388,97 @@ class SocketIO:
raise raise
return message return message
def pollresponse(self, myseq, wait=0.0): def pollresponse(self, myseq, wait):
"""Handle messages received on the socket. """Handle messages received on the socket.
Some messages received may be asynchronous 'call' commands, and Some messages received may be asynchronous 'call' or 'queue' requests,
some may be responses intended for other threads. and some may be responses for other threads.
'call' requests are passed to self.localcall() with the expectation of
immediate execution, during which time the socket is not serviced.
'queue' requests are used for tasks (which may block or hang) to be
processed in a different thread. These requests are fed into
request_queue by self.localcall(). Responses to queued requests are
taken from response_queue and sent across the link with the associated
sequence numbers. Messages in the queues are (sequence_number,
request/response) tuples and code using this module removing messages
from the request_queue is responsible for returning the correct
sequence number in the response_queue.
Loop until message with myseq sequence number is received. Save others pollresponse() will loop until a response message with the myseq
in self.responses and notify the owning thread, except that 'call' sequence number is received, and will save other responses in
commands are handed off to localcall() and the response sent back self.responses and notify the owning thread.
across the link with the appropriate sequence number.
""" """
while 1: while 1:
# send queued response if there is one available
try:
qmsg = response_queue.get(0)
except Queue.Empty:
pass
else:
seq, response = qmsg
message = (seq, ('OK', response))
self.putmessage(message)
# poll for message on link
try:
message = self.pollmessage(wait) message = self.pollmessage(wait)
if message is None: # socket not ready if message is None: # socket not ready
return None return None
#wait = 0.0 # poll on subsequent passes instead of blocking except EOFError:
self.handle_EOF()
return None
except AttributeError:
return None
seq, resq = message seq, resq = message
how = resq[0]
self.debug("pollresponse:%d:myseq:%s" % (seq, myseq)) self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
if resq[0] == "call": # process or queue a request
if how in ("CALL", "QUEUE"):
self.debug("pollresponse:%d:localcall:call:" % seq) self.debug("pollresponse:%d:localcall:call:" % seq)
response = self.localcall(resq) response = self.localcall(seq, resq)
self.debug("pollresponse:%d:localcall:response:%s" self.debug("pollresponse:%d:localcall:response:%s"
% (seq, response)) % (seq, response))
if how == "CALL":
self.putmessage((seq, response)) self.putmessage((seq, response))
elif how == "QUEUE":
# don't acknowledge the 'queue' request!
pass
continue continue
# return if completed message transaction
elif seq == myseq: elif seq == myseq:
return resq return resq
# must be a response for a different thread:
else: else:
self.cvar.acquire() cv = self.cvars.get(seq, None)
cv = self.cvars.get(seq)
# response involving unknown sequence number is discarded, # response involving unknown sequence number is discarded,
# probably intended for prior incarnation # probably intended for prior incarnation of server
if cv is not None: if cv is not None:
cv.acquire()
self.responses[seq] = resq self.responses[seq] = resq
cv.notify() cv.notify()
self.cvar.release() cv.release()
continue continue
def handle_EOF(self):
"action taken upon link being closed by peer"
self.EOFhook()
self.debug("handle_EOF")
for key in self.cvars:
cv = self.cvars[key]
cv.acquire()
self.responses[key] = ('EOF', None)
cv.notify()
cv.release()
interrupt.interrupt_main()
# call our (possibly overridden) exit function
self.exithook()
def EOFhook(self):
"Classes using rpc client/server can override to augment EOF action"
pass
#----------------- end class SocketIO -------------------- #----------------- end class SocketIO --------------------
class RemoteObject: class RemoteObject:
...@@ -465,7 +557,8 @@ class RPCProxy: ...@@ -465,7 +557,8 @@ class RPCProxy:
self.__getattributes() self.__getattributes()
if not self.__attributes.has_key(name): if not self.__attributes.has_key(name):
raise AttributeError, name raise AttributeError, name
__getattr__.DebuggerStepThrough=1
__getattr__.DebuggerStepThrough = 1
def __getattributes(self): def __getattributes(self):
self.__attributes = self.sockio.remotecall(self.oid, self.__attributes = self.sockio.remotecall(self.oid,
......
import sys import sys
import os
import time import time
import socket import socket
import traceback import traceback
...@@ -20,12 +21,8 @@ import __main__ ...@@ -20,12 +21,8 @@ import __main__
# the socket) and the main thread (which runs user code), plus global # the socket) and the main thread (which runs user code), plus global
# completion and exit flags: # completion and exit flags:
server = None # RPCServer instance
queue = Queue.Queue(0)
execution_finished = False
exit_requested = False exit_requested = False
def main(): def main():
"""Start the Python execution server in a subprocess """Start the Python execution server in a subprocess
...@@ -44,8 +41,6 @@ def main(): ...@@ -44,8 +41,6 @@ def main():
register and unregister themselves. register and unregister themselves.
""" """
global queue, execution_finished, exit_requested
port = 8833 port = 8833
if sys.argv[1:]: if sys.argv[1:]:
port = int(sys.argv[1]) port = int(sys.argv[1])
...@@ -58,21 +53,23 @@ def main(): ...@@ -58,21 +53,23 @@ def main():
while 1: while 1:
try: try:
if exit_requested: if exit_requested:
sys.exit() os._exit(0)
# XXX KBK 22Mar03 eventually check queue here! try:
pass seq, request = rpc.request_queue.get(0)
except Queue.Empty:
time.sleep(0.05) time.sleep(0.05)
continue
method, args, kwargs = request
ret = method(*args, **kwargs)
rpc.response_queue.put((seq, ret))
except KeyboardInterrupt: except KeyboardInterrupt:
##execution_finished = True
continue continue
def manage_socket(address): def manage_socket(address):
global server, exit_requested
for i in range(6): for i in range(6):
time.sleep(i) time.sleep(i)
try: try:
server = rpc.RPCServer(address, MyHandler) server = MyRPCServer(address, MyHandler)
break break
except socket.error, err: except socket.error, err:
if i < 3: if i < 3:
...@@ -82,10 +79,41 @@ def manage_socket(address): ...@@ -82,10 +79,41 @@ def manage_socket(address):
+ err[1] + ", retrying...." + err[1] + ", retrying...."
else: else:
print>>sys.__stderr__, "\nConnection to Idle failed, exiting." print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
global exit_requested
exit_requested = True exit_requested = True
return
server.handle_request() # A single request only 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): class MyHandler(rpc.RPCHandler):
def handle(self): def handle(self):
...@@ -95,7 +123,20 @@ class MyHandler(rpc.RPCHandler): ...@@ -95,7 +123,20 @@ class MyHandler(rpc.RPCHandler):
sys.stdin = self.get_remote_proxy("stdin") sys.stdin = self.get_remote_proxy("stdin")
sys.stdout = self.get_remote_proxy("stdout") sys.stdout = self.get_remote_proxy("stdout")
sys.stderr = self.get_remote_proxy("stderr") 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: class Executive:
...@@ -106,44 +147,30 @@ class Executive: ...@@ -106,44 +147,30 @@ class Executive:
self.calltip = CallTips.CallTips() self.calltip = CallTips.CallTips()
def runcode(self, code): 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: try:
exec code in self.locals exec code in self.locals
except: except:
try:
if exit_requested:
os._exit(0)
self.flush_stdout() self.flush_stdout()
efile = sys.stderr efile = sys.stderr
typ, val, tb = info = sys.exc_info() typ, val, tb = info = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = info sys.last_type, sys.last_value, sys.last_traceback = info
tbe = traceback.extract_tb(tb) tbe = traceback.extract_tb(tb)
print >>efile, 'Traceback (most recent call last):' 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) self.cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile) traceback.print_list(tbe, file=efile)
lines = traceback.format_exception_only(typ, val) lines = traceback.format_exception_only(typ, val)
for line in lines: for line in lines:
print>>efile, line, print>>efile, line,
execution_finished = True except:
sys.stderr = sys.__stderr__
raise
else: else:
self.flush_stdout() self.flush_stdout()
execution_finished = True
def flush_stdout(self): def flush_stdout(self):
try: try:
...@@ -184,15 +211,8 @@ class Executive: ...@@ -184,15 +211,8 @@ class Executive:
tb[i] = fn, ln, nm, line tb[i] = fn, ln, nm, line
def interrupt_the_server(self): def interrupt_the_server(self):
self.rpchandler.interrupted = True
##print>>sys.__stderr__, "** Interrupt main!"
interrupt.interrupt_main() interrupt.interrupt_main()
def shutdown_the_server(self):
global exit_requested
exit_requested = True
def start_the_debugger(self, gui_adap_oid): def start_the_debugger(self, gui_adap_oid):
return RemoteDebugger.start_debugger(self.rpchandler, 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