Commit 00e1e290 authored by Jim Fulton's avatar Jim Fulton

Added support for message iterators. This allows one, for example, to

use an iterator to send a large file without loading it in memory.
parent b01f4057
...@@ -101,7 +101,7 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -101,7 +101,7 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
self.__state = 0 self.__state = 0
self.__has_mac = 0 self.__has_mac = 0
self.__msg_size = 4 self.__msg_size = 4
self.__output_lock = threading.Lock() # Protects __output self.__output_messages = []
self.__output = [] self.__output = []
self.__closed = False self.__closed = False
# Each side of the connection sends and receives messages. A # Each side of the connection sends and receives messages. A
...@@ -129,9 +129,31 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -129,9 +129,31 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
def setSessionKey(self, sesskey): def setSessionKey(self, sesskey):
log("set session key %r" % sesskey) log("set session key %r" % sesskey)
self.__hmac_send = hmac.HMAC(sesskey, digestmod=sha)
self.__hmac_recv = hmac.HMAC(sesskey, digestmod=sha)
# Low-level construction is now delayed until data are sent.
# This is to allow use of iterators that generate messages
# only when we're ready to do I/O so that we can effeciently
# transmit large files. Because we delay messages, we also
# have to delay setting the session key to retain proper
# ordering.
# The low-level output queue supports strings, a special close
# marker, and iterators. It doesn't support callbacks. We
# can create a allback by providing an iterator that doesn't
# yield anything.
# The hack fucntion below is a callback in iterator's
# clothing. :) It never yields anything, but is a generator
# and thus iterator, because it contains a yield statement.
def hack():
self.__hmac_send = hmac.HMAC(sesskey, digestmod=sha)
self.__hmac_recv = hmac.HMAC(sesskey, digestmod=sha)
if False:
yield ''
self.message_output(hack())
def get_addr(self): def get_addr(self):
return self.addr return self.addr
...@@ -232,86 +254,90 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -232,86 +254,90 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
return True return True
def writable(self): def writable(self):
if len(self.__output) == 0: return bool(self.__output_messages or self.__output)
return False
else:
return True
def should_close(self): def should_close(self):
self.__output.append(_close_marker) self.__output_messages.append(_close_marker)
def handle_write(self): def handle_write(self):
self.__output_lock.acquire() output = self.__output
try: messages = self.__output_messages
output = self.__output while output or messages:
while output:
# Accumulate output into a single string so that we avoid # Process queued messages until we have enough output
# multiple send() calls, but avoid accumulating too much size = sum((len(s) for s in output))
# data. If we send a very small string and have more data while (size <= SEND_SIZE) and messages:
# to send, we will likely incur delays caused by the message = messages[0]
# unfortunate interaction between the Nagle algorithm and if message.__class__ is str:
# delayed acks. If we send a very large string, only a size += self.__message_output(messages.pop(0), output)
# portion of it will actually be delivered at a time. elif message is _close_marker:
del messages[:]
l = 0 del output[:]
for i in range(len(output)): return self.close()
else:
try: try:
l += len(output[i]) message = message.next()
except TypeError: except StopIteration:
# We had an output marker, close the connection messages.pop(0)
assert output[i] is _close_marker else:
return self.close() size += self.__message_output(message, output)
if l > SEND_SIZE: # Accumulate output into a single string so that we avoid
break # multiple send() calls, but avoid accumulating too much
# data. If we send a very small string and have more data
i += 1 # to send, we will likely incur delays caused by the
# It is very unlikely that i will be 1. # unfortunate interaction between the Nagle algorithm and
v = "".join(output[:i]) # delayed acks. If we send a very large string, only a
del output[:i] # portion of it will actually be delivered at a time.
l = 0
try: for i in range(len(output)):
n = self.send(v) l += len(output[i])
except socket.error, err: if l > SEND_SIZE:
if err[0] in expected_socket_write_errors: break
break # we couldn't write anything
raise i += 1
if n < len(v): # It is very unlikely that i will be 1.
output.insert(0, v[n:]) v = "".join(output[:i])
break # we can't write any more del output[:i]
finally:
self.__output_lock.release() try:
n = self.send(v)
except socket.error, err:
if err[0] in expected_socket_write_errors:
break # we couldn't write anything
raise
if n < l:
output.insert(0, v[n:])
break # we can't write any more
def handle_close(self): def handle_close(self):
self.close() self.close()
def message_output(self, message): def message_output(self, message):
if __debug__:
if self._debug:
log("message_output %d bytes: %s hmac=%d" %
(len(message), short_repr(message),
self.__hmac_send and 1 or 0),
level=TRACE)
if self.__closed: if self.__closed:
raise DisconnectedError( raise DisconnectedError(
"This action is temporarily unavailable.<p>") "This action is temporarily unavailable.<p>")
self.__output_lock.acquire() self.__output_messages.append(message)
try:
# do two separate appends to avoid copying the message string def __message_output(self, message, output):
if self.__hmac_send: # do two separate appends to avoid copying the message string
self.__output.append(struct.pack(">I", len(message) | MAC_BIT)) size = 4
self.__hmac_send.update(message) if self.__hmac_send:
self.__output.append(self.__hmac_send.digest()) output.append(struct.pack(">I", len(message) | MAC_BIT))
else: self.__hmac_send.update(message)
self.__output.append(struct.pack(">I", len(message))) output.append(self.__hmac_send.digest())
if len(message) <= SEND_SIZE: size += 20
self.__output.append(message) else:
else: output.append(struct.pack(">I", len(message)))
for i in range(0, len(message), SEND_SIZE):
self.__output.append(message[i:i+SEND_SIZE]) if len(message) <= SEND_SIZE:
finally: output.append(message)
self.__output_lock.release() else:
for i in range(0, len(message), SEND_SIZE):
output.append(message[i:i+SEND_SIZE])
return size + len(message)
def close(self): def close(self):
if not self.__closed: if not self.__closed:
......
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