libev: handle EAGAIN when message len matches buffer size

This commit is contained in:
Tyler Hobbs
2014-02-18 15:07:43 -06:00
parent 6bd519b8f9
commit ec95133ef4
3 changed files with 41 additions and 2 deletions

View File

@@ -9,6 +9,10 @@ Bug Fixes
* Always close socket when defuncting error'ed connections to avoid a potential * Always close socket when defuncting error'ed connections to avoid a potential
file descriptor leak file descriptor leak
* Handle "custom" types (such as the replaced DateType) correctly * Handle "custom" types (such as the replaced DateType) correctly
* With libevreactor, correctly handle EAGAIN/EWOULDBLOCK when the message from
Cassandra is a multiple of the read buffer size. Previously, if no more data
became available to read on the socket, the message would never be processed,
resulting in an OperationTimedOut error.
Other Other
----- -----

View File

@@ -313,7 +313,7 @@ class LibevConnection(Connection):
except socket.error as err: except socket.error as err:
if err.args[0] not in NONBLOCKING: if err.args[0] not in NONBLOCKING:
self.defunct(err) self.defunct(err)
return return
if self._iobuf.tell(): if self._iobuf.tell():
while True: while True:

View File

@@ -4,6 +4,7 @@ except ImportError:
import unittest # noqa import unittest # noqa
import errno import errno
import os
from StringIO import StringIO from StringIO import StringIO
from socket import error as socket_error from socket import error as socket_error
@@ -15,7 +16,7 @@ from cassandra.connection import (PROTOCOL_VERSION,
from cassandra.decoder import (write_stringmultimap, write_int, write_string, from cassandra.decoder import (write_stringmultimap, write_int, write_string,
SupportedMessage, ReadyMessage, ServerError) SupportedMessage, ReadyMessage, ServerError)
from cassandra.marshal import uint8_pack, uint32_pack from cassandra.marshal import uint8_pack, uint32_pack, int32_pack
try: try:
from cassandra.io.libevreactor import LibevConnection from cassandra.io.libevreactor import LibevConnection
@@ -85,6 +86,40 @@ class LibevConnectionTest(unittest.TestCase):
c.handle_read(None, 0) c.handle_read(None, 0)
self.assertTrue(c.connected_event.is_set()) self.assertTrue(c.connected_event.is_set())
return c
def test_egain_on_buffer_size(self, *args):
# get a connection that's already fully started
c = self.test_successful_connection()
header = '\x00\x00\x00\x00' + int32_pack(20000)
responses = [
header + ('a' * (4096 - len(header))),
'a' * 4096,
socket_error(errno.EAGAIN),
'a' * 100,
socket_error(errno.EAGAIN)]
def side_effect(*args):
response = responses.pop(0)
if isinstance(response, socket_error):
raise response
else:
return response
c._socket.recv.side_effect = side_effect
c.handle_read(None, 0)
self.assertEquals(c._total_reqd_bytes, 20000 + len(header))
# the EAGAIN prevents it from reading the last 100 bytes
c._iobuf.seek(0, os.SEEK_END)
pos = c._iobuf.tell()
self.assertEquals(pos, 4096 + 4096)
# now tell it to read the last 100 bytes
c.handle_read(None, 0)
c._iobuf.seek(0, os.SEEK_END)
pos = c._iobuf.tell()
self.assertEquals(pos, 4096 + 4096 + 100)
def test_protocol_error(self, *args): def test_protocol_error(self, *args):
c = self.make_connection() c = self.make_connection()