summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES4
-rw-r--r--redis/__init__.py2
-rw-r--r--redis/_compat.py50
-rwxr-xr-xredis/connection.py12
4 files changed, 62 insertions, 6 deletions
diff --git a/CHANGES b/CHANGES
index 37b2830..62493fb 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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,