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 <a...@gmx.de> + +- 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()