diff options
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | redis/__init__.py | 2 | ||||
-rw-r--r-- | redis/_compat.py | 50 | ||||
-rwxr-xr-x | redis/connection.py | 12 |
4 files changed, 62 insertions, 6 deletions
@@ -1,3 +1,7 @@ +* 3.3.9 + * Mapped Python 2.7 SSLError to TimeoutError where appropriate. Timeouts + should now consistently raise TimeoutErrors on Python 2.7 for both + unsecured and secured connections. Thanks @zbristow. #1222 * 3.3.8 * Fixed MONITOR parsing to properly parse IPv6 client addresses, unix socket connections and commands issued from Lua. Thanks @kukey. #1201 diff --git a/redis/__init__.py b/redis/__init__.py index 9395147..2835a42 100644 --- a/redis/__init__.py +++ b/redis/__init__.py @@ -29,7 +29,7 @@ def int_or_str(value): return value -__version__ = '3.3.8' +__version__ = '3.3.9' VERSION = tuple(map(int_or_str, __version__.split('.'))) __all__ = [ diff --git a/redis/_compat.py b/redis/_compat.py index d70af2a..39b6619 100644 --- a/redis/_compat.py +++ b/redis/_compat.py @@ -3,6 +3,19 @@ import errno import socket import sys + +def sendall(sock, *args, **kwargs): + return sock.sendall(*args, **kwargs) + + +def shutdown(sock, *args, **kwargs): + return sock.shutdown(*args, **kwargs) + + +def ssl_wrap_socket(context, sock, *args, **kwargs): + return context.wrap_socket(sock, *args, **kwargs) + + # For Python older than 3.5, retry EINTR. if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 5): @@ -61,6 +74,43 @@ else: # Python 3.5 and above automatically retry EINTR return sock.recv_into(*args, **kwargs) if sys.version_info[0] < 3: + # In Python 3, the ssl module raises socket.timeout whereas it raises + # SSLError in Python 2. For compatibility between versions, ensure + # socket.timeout is raised for both. + import functools + + try: + from ssl import SSLError as _SSLError + except ImportError: + class _SSLError(Exception): + """A replacement in case ssl.SSLError is not available.""" + pass + + _EXPECTED_SSL_TIMEOUT_MESSAGES = ( + "The handshake operation timed out", + "The read operation timed out", + "The write operation timed out", + ) + + def _handle_ssl_timeout(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except _SSLError as e: + if any(x in e.args[0] for x in _EXPECTED_SSL_TIMEOUT_MESSAGES): + # Raise socket.timeout for compatibility with Python 3. + raise socket.timeout(*e.args) + raise + return wrapper + + recv = _handle_ssl_timeout(recv) + recv_into = _handle_ssl_timeout(recv_into) + sendall = _handle_ssl_timeout(sendall) + shutdown = _handle_ssl_timeout(shutdown) + ssl_wrap_socket = _handle_ssl_timeout(ssl_wrap_socket) + +if sys.version_info[0] < 3: from urllib import unquote from urlparse import parse_qs, urlparse from itertools import imap, izip diff --git a/redis/connection.py b/redis/connection.py index 333bd75..feea041 100755 --- a/redis/connection.py +++ b/redis/connection.py @@ -13,7 +13,8 @@ import warnings from redis._compat import (xrange, imap, byte_to_chr, unicode, long, nativestr, basestring, iteritems, LifoQueue, Empty, Full, urlparse, parse_qs, - recv, recv_into, unquote, BlockingIOError) + recv, recv_into, unquote, BlockingIOError, + sendall, shutdown, ssl_wrap_socket) from redis.exceptions import ( AuthenticationError, BusyLoadingError, @@ -630,7 +631,7 @@ class Connection(object): return try: if os.getpid() == self.pid: - self._sock.shutdown(socket.SHUT_RDWR) + shutdown(self._sock, socket.SHUT_RDWR) self._sock.close() except socket.error: pass @@ -662,7 +663,7 @@ class Connection(object): if isinstance(command, str): command = [command] for item in command: - self._sock.sendall(item) + sendall(self._sock, item) except socket.timeout: self.disconnect() raise TimeoutError("Timeout writing to socket") @@ -815,11 +816,12 @@ class SSLConnection(Connection): keyfile=self.keyfile) if self.ca_certs: context.load_verify_locations(self.ca_certs) - sock = context.wrap_socket(sock, server_hostname=self.host) + sock = ssl_wrap_socket(context, sock, server_hostname=self.host) else: # In case this code runs in a version which is older than 2.7.9, # we want to fall back to old code - sock = ssl.wrap_socket(sock, + sock = ssl_wrap_socket(ssl, + sock, cert_reqs=self.cert_reqs, keyfile=self.keyfile, certfile=self.certfile, |