Add a send lock to the base Connection class

The sendRaw method (and therefore sendPacket) was originally
thread safe by virtue of consisting of a single socket.send()
call, but when we added SSL, we added a loop within the method
to handle the increased likelihood that not all data would be
sent in one call.  Of course, the method should have been written
this way to start with.

However, this means that we can end up with partial packets being
sent before a context switch to another thread which may also
want to send a packet.  To handle that case, place the entire
method in a lock.

Note, this doesn't affect server connections as they use a
non-blocking connection which has a send queue, so only one thread
ever actuall transmits.

Change-Id: I3bda6fda5f762d18f28b56a43b7dc28f37dbc427
This commit is contained in:
James E. Blair 2017-09-30 08:45:01 -07:00
parent bf8d96cb77
commit b24b4d2f6e
1 changed files with 13 additions and 11 deletions

View File

@ -142,6 +142,7 @@ class Connection(object):
self.input_buffer = b''
self.need_bytes = False
self.echo_lock = threading.Lock()
self.send_lock = threading.Lock()
self._init()
def _init(self):
@ -237,17 +238,18 @@ class Connection(object):
:arg bytes data The raw data to send
"""
while True:
try:
self.conn.send(data)
except ssl.SSLError as e:
if e.errno == ssl.SSL_ERROR_WANT_READ:
continue
elif e.errno == ssl.SSL_ERROR_WANT_WRITE:
continue
else:
raise
break
with self.send_lock:
sent = 0
while sent < len(data):
try:
sent += self.conn.send(data)
except ssl.SSLError as e:
if e.errno == ssl.SSL_ERROR_WANT_READ:
continue
elif e.errno == ssl.SSL_ERROR_WANT_WRITE:
continue
else:
raise
def sendPacket(self, packet):
"""Send a packet to the server.