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()


Reply via email to