Commit 121f2e20 authored by Guido van Rossum's avatar Guido van Rossum

This checkin contains changes (by me) that will allow multiple

parallel outstanding calls.  However it also contains code (by Jeremy,
with one notifyAll() call added by me) that enforces the old rule of a
single outstanding call.  This is hopefully unnecessessary, but we
haven't reviewed the server side yet to make sure that that's really
the case (the server was until now getting serialized calls per
connection).
parent f89b404b
...@@ -74,14 +74,12 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -74,14 +74,12 @@ class Connection(smac.SizedMessageAsyncConnection):
and asynchronous calls, which do not. and asynchronous calls, which do not.
It uses the Marshaller class to handle encoding and decoding of It uses the Marshaller class to handle encoding and decoding of
method calls are arguments. Marshaller uses pickle to encode method calls and arguments. Marshaller uses pickle to encode
arbitrary Python objects. The code here doesn't ever see the wire arbitrary Python objects. The code here doesn't ever see the wire
format. format.
A Connection is designed for use in a multithreaded application, A Connection is designed for use in a multithreaded application,
where a synchronous call must block until a response is ready. where a synchronous call must block until a response is ready.
The current design only allows a single synchronous call to be
outstanding.
A socket connection between a client and a server allows either A socket connection between a client and a server allows either
side to invoke methods on the other side. The processes on each side to invoke methods on the other side. The processes on each
...@@ -137,13 +135,10 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -137,13 +135,10 @@ class Connection(smac.SizedMessageAsyncConnection):
self._map = {self._fileno: self} self._map = {self._fileno: self}
# __msgid_lock guards access to msgid # __msgid_lock guards access to msgid
self.__msgid_lock = threading.Lock() self.__msgid_lock = threading.Lock()
# __call_lock prevents more than one synchronous call from # __replies_cond is used to block when a synchronous call is
# being issued at one time.
self.__call_lock = threading.Lock()
# __reply_lock is used to block when a synchronous call is
# waiting for a response # waiting for a response
self.__reply_lock = threading.Lock() self.__replies_cond = threading.Condition()
self.__reply_lock.acquire() self.__replies = {}
self.register_object(obj) self.register_object(obj)
self.handshake() self.handshake()
...@@ -153,6 +148,7 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -153,6 +148,7 @@ class Connection(smac.SizedMessageAsyncConnection):
def close(self): def close(self):
if self.closed: if self.closed:
return return
self._map.clear()
self.closed = 1 self.closed = 1
self.close_trigger() self.close_trigger()
self.__super_close() self.__super_close()
...@@ -183,6 +179,9 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -183,6 +179,9 @@ class Connection(smac.SizedMessageAsyncConnection):
def recv_handshake(self, message): def recv_handshake(self, message):
if message == self.protocol_version: if message == self.protocol_version:
self.message_input = self._message_input self.message_input = self._message_input
else:
log("recv_handshake: bad handshake %s" % repr(message),
level=zLOG.ERROR)
# otherwise do something else... # otherwise do something else...
def message_input(self, message): def message_input(self, message):
...@@ -208,8 +207,12 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -208,8 +207,12 @@ class Connection(smac.SizedMessageAsyncConnection):
if __debug__: if __debug__:
log("recv reply: %s, %s, %s" % (msgid, flags, short_repr(args)), log("recv reply: %s, %s, %s" % (msgid, flags, short_repr(args)),
level=zLOG.DEBUG) level=zLOG.DEBUG)
self.__reply = msgid, flags, args self.__replies_cond.acquire()
self.__reply_lock.release() # will fail if lock is unlocked try:
self.__replies[msgid] = flags, args
self.__replies_cond.notifyAll()
finally:
self.__replies_cond.release()
def handle_request(self, msgid, flags, name, args): def handle_request(self, msgid, flags, name, args):
if not self.check_method(name): if not self.check_method(name):
...@@ -225,6 +228,10 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -225,6 +228,10 @@ class Connection(smac.SizedMessageAsyncConnection):
raise raise
except Exception, msg: except Exception, msg:
error = sys.exc_info() error = sys.exc_info()
# XXX Since we're just passing this on to the caller, and
# there are several cases where this happens during the
# normal course of action, shouldn't this be logged at the
# INFO level?
log("%s() raised exception: %s" % (name, msg), zLOG.ERROR, log("%s() raised exception: %s" % (name, msg), zLOG.ERROR,
error=error) error=error)
error = error[:2] error = error[:2]
...@@ -298,22 +305,19 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -298,22 +305,19 @@ class Connection(smac.SizedMessageAsyncConnection):
return msgid return msgid
def call(self, method, *args): def call(self, method, *args):
self.__call_lock.acquire() self.__replies_cond.acquire()
try: try:
return self._call(method, args) while self.__replies and not self.closed:
log("waiting for previous call to finish %s" %
repr(self.__replies.values()[0]))
self.__replies_cond.wait(30)
if self.closed:
raise DisconnectedError()
msgid = self.send_call(method, args, 0)
self.__replies[msgid] = None
finally: finally:
self.__call_lock.release() self.__replies_cond.release()
r_flags, r_args = self.wait(msgid)
def _call(self, method, args):
if self.closed:
raise DisconnectedError()
msgid = self.send_call(method, args, 0)
self.__reply = None
self.wait() # will release reply lock before returning
r_msgid, r_flags, r_args = self.__reply
self.__reply_lock.acquire()
assert r_msgid == msgid, "%s != %s: %s" % (r_msgid, msgid, r_args)
if (isinstance(r_args, types.TupleType) if (isinstance(r_args, types.TupleType)
and type(r_args[0]) == types.ClassType and type(r_args[0]) == types.ClassType
and issubclass(r_args[0], Exception)): and issubclass(r_args[0], Exception)):
...@@ -347,27 +351,39 @@ class Connection(smac.SizedMessageAsyncConnection): ...@@ -347,27 +351,39 @@ class Connection(smac.SizedMessageAsyncConnection):
else: else:
return 0 return 0
def wait(self): def wait(self, msgid):
"""Invoke asyncore mainloop and wait for reply.""" """Invoke asyncore mainloop and wait for reply."""
if __debug__: if __debug__:
log("wait() async=%d" % self.is_async(), level=zLOG.TRACE) log("wait() async=%d" % self.is_async(), level=zLOG.TRACE)
if self.is_async(): if self.is_async():
self.trigger.pull_trigger() self.trigger.pull_trigger()
self.__reply_lock.acquire()
# wait until reply... self.__replies_cond.acquire()
else: try:
# Do loop until asyncore handler unlocks the lock. while 1:
assert not self.__reply_lock.acquire(0)
while not self.__reply_lock.acquire(0):
try:
asyncore.poll(10.0, self._map)
except select.error, err:
log("Closing. asyncore.poll() raised %s." % err,
level=zLOG.BLATHER)
self.close()
if self.closed: if self.closed:
raise DisconnectedError() raise DisconnectedError()
self.__reply_lock.release() reply = self.__replies.get(msgid)
if reply is not None:
del self.__replies[msgid]
assert len(self.__replies) == 0
self.__replies_cond.notifyAll()
return reply
if self.is_async():
self.__replies_cond.wait(10.0)
else:
self.__replies_cond.release()
try:
try:
asyncore.poll(10.0, self._map)
except select.error, err:
log("Closing. asyncore.poll() raised %s." % err,
level=zLOG.BLATHER)
self.close()
finally:
self.__replies_cond.acquire()
finally:
self.__replies_cond.release()
def poll(self, wait_for_reply=0): def poll(self, wait_for_reply=0):
"""Invoke asyncore mainloop to get pending message out.""" """Invoke asyncore mainloop to get pending message out."""
......
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