Hello community,
here is the log from the commit of package python-cassandra-driver for
openSUSE:Factory checked in at 2019-05-06 13:28:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cassandra-driver (Old)
and /work/SRC/openSUSE:Factory/.python-cassandra-driver.new.5148 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cassandra-driver"
Mon May 6 13:28:21 2019 rev:8 rq:700973 version:3.17.1
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-cassandra-driver/python-cassandra-driver.changes
2019-03-04 09:22:45.872575184 +0100
+++
/work/SRC/openSUSE:Factory/.python-cassandra-driver.new.5148/python-cassandra-driver.changes
2019-05-06 13:28:25.141407151 +0200
@@ -1,0 +2,8 @@
+Sat May 4 19:56:15 UTC 2019 - Arun Persaud <[email protected]>
+
+- update to version 3.17.1:
+ * Bug Fixes
+ + Socket errors EAGAIN/EWOULDBLOCK are not handled properly and
+ cause timeouts (PYTHON-1089)
+
+-------------------------------------------------------------------
Old:
----
3.17.0.tar.gz
New:
----
3.17.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-cassandra-driver.spec ++++++
--- /var/tmp/diff_new_pack.whD4Ux/_old 2019-05-06 13:28:25.609408170 +0200
+++ /var/tmp/diff_new_pack.whD4Ux/_new 2019-05-06 13:28:25.609408170 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-cassandra-driver
-Version: 3.17.0
+Version: 3.17.1
Release: 0
Summary: Python driver for Cassandra
License: Apache-2.0
++++++ 3.17.0.tar.gz -> 3.17.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/CHANGELOG.rst
new/python-driver-3.17.1/CHANGELOG.rst
--- old/python-driver-3.17.0/CHANGELOG.rst 2019-02-15 22:05:51.000000000
+0100
+++ new/python-driver-3.17.1/CHANGELOG.rst 2019-05-02 13:27:51.000000000
+0200
@@ -1,3 +1,11 @@
+3.17.1
+======
+May 2, 2019
+
+Bug Fixes
+---------
+* Socket errors EAGAIN/EWOULDBLOCK are not handled properly and cause timeouts
(PYTHON-1089)
+
3.17.0
======
February 19, 2019
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/build.yaml
new/python-driver-3.17.1/build.yaml
--- old/python-driver-3.17.0/build.yaml 2019-02-15 22:05:51.000000000 +0100
+++ new/python-driver-3.17.1/build.yaml 2019-05-02 13:27:51.000000000 +0200
@@ -43,6 +43,17 @@
- python: [3.4, 3.6]
- cassandra: ['2.0', '2.1', '3.0']
+ commit_branches:
+ schedule: per_commit
+ branches:
+ include: [/.+release/]
+ env_vars: |
+ EVENT_LOOP_MANAGER='libev'
+ EXCLUDE_LONG=1
+ matrix:
+ exclude:
+ - python: [3.4, 3.6]
+
weekly_libev:
schedule: 0 10 * * 6
branches:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/cassandra/__init__.py
new/python-driver-3.17.1/cassandra/__init__.py
--- old/python-driver-3.17.0/cassandra/__init__.py 2019-02-15
22:05:51.000000000 +0100
+++ new/python-driver-3.17.1/cassandra/__init__.py 2019-05-02
13:27:51.000000000 +0200
@@ -22,7 +22,7 @@
logging.getLogger('cassandra').addHandler(NullHandler())
-__version_info__ = (3, 17, 0)
+__version_info__ = (3, 17, 1)
__version__ = '.'.join(map(str, __version_info__))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/cassandra/io/asyncorereactor.py
new/python-driver-3.17.1/cassandra/io/asyncorereactor.py
--- old/python-driver-3.17.0/cassandra/io/asyncorereactor.py 2019-02-15
22:05:51.000000000 +0100
+++ new/python-driver-3.17.1/cassandra/io/asyncorereactor.py 2019-05-02
13:27:51.000000000 +0200
@@ -426,12 +426,14 @@
except socket.error as err:
if ssl and isinstance(err, ssl.SSLError):
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
- return
+ if not self._iobuf.tell():
+ return
else:
self.defunct(err)
return
elif err.args[0] in NONBLOCKING:
- return
+ if not self._iobuf.tell():
+ return
else:
self.defunct(err)
return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/cassandra/io/libevreactor.py
new/python-driver-3.17.1/cassandra/io/libevreactor.py
--- old/python-driver-3.17.0/cassandra/io/libevreactor.py 2019-02-15
22:05:51.000000000 +0100
+++ new/python-driver-3.17.1/cassandra/io/libevreactor.py 2019-05-02
13:27:51.000000000 +0200
@@ -346,12 +346,14 @@
except socket.error as err:
if ssl and isinstance(err, ssl.SSLError):
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
- return
+ if not self._iobuf.tell():
+ return
else:
self.defunct(err)
return
elif err.args[0] in NONBLOCKING:
- return
+ if not self._iobuf.tell():
+ return
else:
self.defunct(err)
return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-driver-3.17.0/tests/unit/io/utils.py
new/python-driver-3.17.1/tests/unit/io/utils.py
--- old/python-driver-3.17.0/tests/unit/io/utils.py 2019-02-15
22:05:51.000000000 +0100
+++ new/python-driver-3.17.1/tests/unit/io/utils.py 2019-05-02
13:27:51.000000000 +0200
@@ -19,7 +19,10 @@
SupportedMessage, ReadyMessage, ServerError)
from tests import is_monkey_patched
+import io
+import random
from functools import wraps
+from itertools import cycle
import six
from six import binary_type, BytesIO
from mock import Mock
@@ -258,42 +261,62 @@
self._check_error_recovery_on_buffer_size(errno.EWOULDBLOCK)
def test_sslwantread_on_buffer_size(self):
- self._check_error_recovery_on_buffer_size(ssl.SSL_ERROR_WANT_READ)
+ self._check_error_recovery_on_buffer_size(
+ ssl.SSL_ERROR_WANT_READ,
+ error_class=ssl.SSLError)
def test_sslwantwrite_on_buffer_size(self):
- self._check_error_recovery_on_buffer_size(ssl.SSL_ERROR_WANT_WRITE)
+ self._check_error_recovery_on_buffer_size(
+ ssl.SSL_ERROR_WANT_WRITE,
+ error_class=ssl.SSLError)
- def _check_error_recovery_on_buffer_size(self, error_code):
+ def _check_error_recovery_on_buffer_size(self, error_code,
error_class=socket_error):
c = self.test_successful_connection()
- header = six.b('\x00\x00\x00\x00') + int32_pack(20000)
- responses = [
- header + (six.b('a') * (4096 - len(header))),
- six.b('a') * 4096,
- socket_error(error_code),
- six.b('a') * 100,
- socket_error(error_code)]
-
- def side_effect(*args):
- response = responses.pop(0)
- log.debug('about to mock return {}'.format(response))
- if isinstance(response, socket_error):
+ # current data, used by the recv side_effect
+ message_chunks = None
+
+ def recv_side_effect(*args):
+ response = message_chunks.pop(0)
+ if isinstance(response, error_class):
raise response
else:
return response
- self.get_socket(c).recv.side_effect = side_effect
- c.handle_read(*self.null_handle_function_args)
- # the EAGAIN prevents it from reading the last 100 bytes
- c._iobuf.seek(0, os.SEEK_END)
- pos = c._iobuf.tell()
- self.assertEqual(pos, 4096 + 4096)
-
- # now tell it to read the last 100 bytes
- c.handle_read(*self.null_handle_function_args)
- c._iobuf.seek(0, os.SEEK_END)
- pos = c._iobuf.tell()
- self.assertEqual(pos, 4096 + 4096 + 100)
+ # setup
+ self.get_socket(c).recv.side_effect = recv_side_effect
+ c.process_io_buffer = Mock()
+
+ def chunk(size):
+ return six.b('a') * size
+
+ buf_size = c.in_buffer_size
+
+ # List of messages to test. A message = (chunks, expected_read_size)
+ messages = [
+ ([chunk(200)], 200),
+ ([chunk(200), chunk(200)], 200), # first chunk < in_buffer_size,
process the message
+ ([chunk(buf_size), error_class(error_code)], buf_size),
+ ([chunk(buf_size), chunk(buf_size), error_class(error_code)],
buf_size*2),
+ ([chunk(buf_size), chunk(buf_size), chunk(10)], (buf_size*2) + 10),
+ ([chunk(buf_size), chunk(buf_size), error_class(error_code),
chunk(10)], buf_size*2),
+ ([error_class(error_code), chunk(buf_size)], 0)
+ ]
+
+ for message, expected_size in messages:
+ message_chunks = message
+ c._iobuf = io.BytesIO()
+ c.process_io_buffer.reset_mock()
+ c.handle_read(*self.null_handle_function_args)
+ c._iobuf.seek(0, os.SEEK_END)
+
+ # Ensure the message size is the good one and that the
+ # message has been processed if it is non-empty
+ self.assertEqual(c._iobuf.tell(), expected_size)
+ if expected_size == 0:
+ c.process_io_buffer.assert_not_called()
+ else:
+ c.process_io_buffer.assert_called_once_with()
def test_protocol_error(self):
c = self.make_connection()
@@ -450,3 +473,41 @@
self.assertTrue(c.connected_event.is_set())
self.assertFalse(c.is_defunct)
+
+ def test_mixed_message_and_buffer_sizes(self):
+ """
+ Validate that all messages are processed with different scenarios:
+
+ - various message sizes
+ - various socket buffer sizes
+ - random non-fatal errors raised
+ """
+ c = self.make_connection()
+ c.process_io_buffer = Mock()
+
+ errors = cycle([
+ ssl.SSLError(ssl.SSL_ERROR_WANT_READ),
+ ssl.SSLError(ssl.SSL_ERROR_WANT_WRITE),
+ socket_error(errno.EWOULDBLOCK),
+ socket_error(errno.EAGAIN)
+ ])
+
+ for buffer_size in [512, 1024, 2048, 4096, 8192]:
+ c.in_buffer_size = buffer_size
+
+ for i in range(1, 15):
+ c.process_io_buffer.reset_mock()
+ c._iobuf = io.BytesIO()
+ message = io.BytesIO(six.b('a') * (2**i))
+
+ def recv_side_effect(*args):
+ if random.randint(1,10) % 3 == 0:
+ raise next(errors)
+ return message.read(args[0])
+
+ self.get_socket(c).recv.side_effect = recv_side_effect
+ c.handle_read(*self.null_handle_function_args)
+ if c._iobuf.tell():
+ c.process_io_buffer.assert_called_once()
+ else:
+ c.process_io_buffer.assert_not_called()