mirror of
https://github.com/python/cpython.git
synced 2025-10-09 16:34:44 +00:00
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
This commit is contained in:
parent
c4607dadce
commit
a00050f209
4 changed files with 267 additions and 193 deletions
|
@ -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.rpcclt.close()
|
||||
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()
|
||||
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()
|
||||
|
|
|
@ -28,17 +28,21 @@ accomplished in Idle.
|
|||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import select
|
||||
import SocketServer
|
||||
import struct
|
||||
import cPickle as pickle
|
||||
import threading
|
||||
import Queue
|
||||
import traceback
|
||||
import copy_reg
|
||||
import types
|
||||
import marshal
|
||||
|
||||
import interrupt
|
||||
|
||||
def unpickle_code(ms):
|
||||
co = marshal.loads(ms)
|
||||
assert isinstance(co, types.CodeType)
|
||||
|
@ -98,8 +102,6 @@ class RPCServer(SocketServer.TCPServer):
|
|||
raise
|
||||
except SystemExit:
|
||||
raise
|
||||
except EOFError:
|
||||
pass
|
||||
except:
|
||||
erf = sys.__stderr__
|
||||
print>>erf, '\n' + '-'*40
|
||||
|
@ -110,28 +112,29 @@ class RPCServer(SocketServer.TCPServer):
|
|||
traceback.print_exc(file=erf)
|
||||
print>>erf, '\n*** Unrecoverable, server exiting!'
|
||||
print>>erf, '-'*40
|
||||
import os
|
||||
os._exit(0)
|
||||
|
||||
#----------------- end class RPCServer --------------------
|
||||
|
||||
objecttable = {}
|
||||
request_queue = Queue.Queue(0)
|
||||
response_queue = Queue.Queue(0)
|
||||
|
||||
|
||||
class SocketIO:
|
||||
|
||||
nextseq = 0
|
||||
|
||||
def __init__(self, sock, objtable=None, debugging=None):
|
||||
self.mainthread = threading.currentThread()
|
||||
self.sockthread = threading.currentThread()
|
||||
if debugging is not None:
|
||||
self.debugging = debugging
|
||||
self.sock = sock
|
||||
if objtable is None:
|
||||
objtable = objecttable
|
||||
self.objtable = objtable
|
||||
self.cvar = threading.Condition()
|
||||
self.responses = {}
|
||||
self.cvars = {}
|
||||
self.interrupted = False
|
||||
|
||||
def close(self):
|
||||
sock = self.sock
|
||||
|
@ -139,6 +142,10 @@ class SocketIO:
|
|||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
def exithook(self):
|
||||
"override for specific exit action"
|
||||
os._exit()
|
||||
|
||||
def debug(self, *args):
|
||||
if not self.debugging:
|
||||
return
|
||||
|
@ -156,13 +163,12 @@ class SocketIO:
|
|||
except KeyError:
|
||||
pass
|
||||
|
||||
def localcall(self, request):
|
||||
def localcall(self, seq, request):
|
||||
self.debug("localcall:", request)
|
||||
try:
|
||||
how, (oid, methodname, args, kwargs) = request
|
||||
except TypeError:
|
||||
return ("ERROR", "Bad request format")
|
||||
assert how == "call"
|
||||
if not self.objtable.has_key(oid):
|
||||
return ("ERROR", "Unknown object id: %s" % `oid`)
|
||||
obj = self.objtable[oid]
|
||||
|
@ -178,14 +184,20 @@ class SocketIO:
|
|||
return ("ERROR", "Unsupported method name: %s" % `methodname`)
|
||||
method = getattr(obj, methodname)
|
||||
try:
|
||||
ret = method(*args, **kwargs)
|
||||
if isinstance(ret, RemoteObject):
|
||||
ret = remoteref(ret)
|
||||
return ("OK", ret)
|
||||
if how == 'CALL':
|
||||
ret = method(*args, **kwargs)
|
||||
if isinstance(ret, RemoteObject):
|
||||
ret = remoteref(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:
|
||||
raise
|
||||
except socket.error:
|
||||
pass
|
||||
raise
|
||||
except:
|
||||
self.debug("localcall:EXCEPTION")
|
||||
traceback.print_exc(file=sys.__stderr__)
|
||||
|
@ -193,24 +205,37 @@ class SocketIO:
|
|||
|
||||
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)
|
||||
|
||||
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):
|
||||
request = ("call", (oid, methodname, args, kwargs))
|
||||
request = ("CALL", (oid, methodname, args, kwargs))
|
||||
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.putmessage((seq, request))
|
||||
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):
|
||||
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)
|
||||
return self.decoderesponse(response)
|
||||
|
||||
|
@ -218,25 +243,36 @@ class SocketIO:
|
|||
how, what = response
|
||||
if how == "OK":
|
||||
return what
|
||||
if how == "QUEUED":
|
||||
return None
|
||||
if how == "EXCEPTION":
|
||||
self.debug("decoderesponse: EXCEPTION")
|
||||
return None
|
||||
if how == "EOF":
|
||||
self.debug("decoderesponse: EOF")
|
||||
self.decode_interrupthook()
|
||||
return None
|
||||
if how == "ERROR":
|
||||
self.debug("decoderesponse: Internal ERROR:", what)
|
||||
raise RuntimeError, what
|
||||
raise SystemError, (how, what)
|
||||
|
||||
def decode_interrupthook(self):
|
||||
""
|
||||
raise EOFError
|
||||
|
||||
def mainloop(self):
|
||||
"""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.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.getresponse(myseq=None, wait=None)
|
||||
self.getresponse(myseq=None, wait=0.05)
|
||||
except EOFError:
|
||||
pass
|
||||
self.debug("mainloop:return")
|
||||
return
|
||||
|
||||
def getresponse(self, myseq, wait):
|
||||
response = self._getresponse(myseq, wait)
|
||||
|
@ -256,23 +292,24 @@ class SocketIO:
|
|||
|
||||
def _getresponse(self, myseq, wait):
|
||||
self.debug("_getresponse:myseq:", myseq)
|
||||
if threading.currentThread() is self.mainthread:
|
||||
# Main thread: does all reading of requests or responses
|
||||
# Loop here, blocking each time until socket is ready.
|
||||
if threading.currentThread() is self.sockthread:
|
||||
# this thread does all reading of requests or responses
|
||||
while 1:
|
||||
response = self.pollresponse(myseq, wait)
|
||||
if response is not None:
|
||||
return response
|
||||
else:
|
||||
# Auxiliary thread: wait for notification from main thread
|
||||
self.cvar.acquire()
|
||||
self.cvars[myseq] = self.cvar
|
||||
# wait for notification from socket handling thread
|
||||
cvar = self.cvars[myseq]
|
||||
cvar.acquire()
|
||||
while not self.responses.has_key(myseq):
|
||||
self.cvar.wait()
|
||||
cvar.wait()
|
||||
response = self.responses[myseq]
|
||||
self.debug("_getresponse:%s: thread woke up: response: %s" %
|
||||
(myseq, response))
|
||||
del self.responses[myseq]
|
||||
del self.cvars[myseq]
|
||||
self.cvar.release()
|
||||
cvar.release()
|
||||
return response
|
||||
|
||||
def newseq(self):
|
||||
|
@ -283,7 +320,7 @@ class SocketIO:
|
|||
self.debug("putmessage:%d:" % message[0])
|
||||
try:
|
||||
s = pickle.dumps(message)
|
||||
except:
|
||||
except pickle.UnpicklingError:
|
||||
print >>sys.__stderr__, "Cannot pickle:", `message`
|
||||
raise
|
||||
s = struct.pack("<i", len(s)) + s
|
||||
|
@ -293,10 +330,13 @@ class SocketIO:
|
|||
except AttributeError:
|
||||
# socket was closed
|
||||
raise IOError
|
||||
except socket.error:
|
||||
self.debug("putmessage:socketerror:pid:%s" % os.getpid())
|
||||
os._exit(0)
|
||||
else:
|
||||
s = s[n:]
|
||||
|
||||
def ioready(self, wait=0.0):
|
||||
def ioready(self, wait):
|
||||
r, w, x = select.select([self.sock.fileno()], [], [], wait)
|
||||
return len(r)
|
||||
|
||||
|
@ -304,7 +344,7 @@ class SocketIO:
|
|||
bufneed = 4
|
||||
bufstate = 0 # meaning: 0 => reading count; 1 => reading data
|
||||
|
||||
def pollpacket(self, wait=0.0):
|
||||
def pollpacket(self, wait):
|
||||
self._stage0()
|
||||
if len(self.buffer) < self.bufneed:
|
||||
if not self.ioready(wait):
|
||||
|
@ -334,7 +374,7 @@ class SocketIO:
|
|||
self.bufstate = 0
|
||||
return packet
|
||||
|
||||
def pollmessage(self, wait=0.0):
|
||||
def pollmessage(self, wait):
|
||||
packet = self.pollpacket(wait)
|
||||
if packet is None:
|
||||
return None
|
||||
|
@ -348,45 +388,97 @@ class SocketIO:
|
|||
raise
|
||||
return message
|
||||
|
||||
def pollresponse(self, myseq, wait=0.0):
|
||||
def pollresponse(self, myseq, wait):
|
||||
"""Handle messages received on the socket.
|
||||
|
||||
Some messages received may be asynchronous 'call' commands, and
|
||||
some may be responses intended for other threads.
|
||||
Some messages received may be asynchronous 'call' or 'queue' requests,
|
||||
and some may be responses for other threads.
|
||||
|
||||
Loop until message with myseq sequence number is received. Save others
|
||||
in self.responses and notify the owning thread, except that 'call'
|
||||
commands are handed off to localcall() and the response sent back
|
||||
across the link with the appropriate sequence number.
|
||||
'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.
|
||||
|
||||
pollresponse() will loop until a response message with the myseq
|
||||
sequence number is received, and will save other responses in
|
||||
self.responses and notify the owning thread.
|
||||
|
||||
"""
|
||||
while 1:
|
||||
message = self.pollmessage(wait)
|
||||
if message is None: # socket not ready
|
||||
# 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)
|
||||
if message is None: # socket not ready
|
||||
return None
|
||||
except EOFError:
|
||||
self.handle_EOF()
|
||||
return None
|
||||
except AttributeError:
|
||||
return None
|
||||
#wait = 0.0 # poll on subsequent passes instead of blocking
|
||||
seq, resq = message
|
||||
how = resq[0]
|
||||
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)
|
||||
response = self.localcall(resq)
|
||||
response = self.localcall(seq, resq)
|
||||
self.debug("pollresponse:%d:localcall:response:%s"
|
||||
% (seq, response))
|
||||
self.putmessage((seq, response))
|
||||
if how == "CALL":
|
||||
self.putmessage((seq, response))
|
||||
elif how == "QUEUE":
|
||||
# don't acknowledge the 'queue' request!
|
||||
pass
|
||||
continue
|
||||
# return if completed message transaction
|
||||
elif seq == myseq:
|
||||
return resq
|
||||
# must be a response for a different thread:
|
||||
else:
|
||||
self.cvar.acquire()
|
||||
cv = self.cvars.get(seq)
|
||||
cv = self.cvars.get(seq, None)
|
||||
# response involving unknown sequence number is discarded,
|
||||
# probably intended for prior incarnation
|
||||
# probably intended for prior incarnation of server
|
||||
if cv is not None:
|
||||
cv.acquire()
|
||||
self.responses[seq] = resq
|
||||
cv.notify()
|
||||
self.cvar.release()
|
||||
cv.release()
|
||||
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 --------------------
|
||||
|
||||
class RemoteObject:
|
||||
|
@ -465,7 +557,8 @@ class RPCProxy:
|
|||
self.__getattributes()
|
||||
if not self.__attributes.has_key(name):
|
||||
raise AttributeError, name
|
||||
__getattr__.DebuggerStepThrough=1
|
||||
|
||||
__getattr__.DebuggerStepThrough = 1
|
||||
|
||||
def __getattributes(self):
|
||||
self.__attributes = self.sockio.remotecall(self.oid,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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
|
||||
time.sleep(0.05)
|
||||
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:
|
||||
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")
|
||||
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
|
||||
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", "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,
|
||||
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)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue