From deed5bfd83264316bc0a734616f1b98fcdbf19de Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 6 Dec 2009 21:49:39 -0800 Subject: [PATCH] Corrected timeout behavior of ssl sockets, better docs on the annoying close behavior. --- eventlet/green/ssl.py | 37 ++++++++++++++++++++++++------------- eventlet/hubs/poll.py | 2 +- tests/stdlib/test_ssl.py | 6 +++--- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/eventlet/green/ssl.py b/eventlet/green/ssl.py index de006fe..9bdedec 100644 --- a/eventlet/green/ssl.py +++ b/eventlet/green/ssl.py @@ -11,12 +11,23 @@ from thread import get_ident from eventlet.greenio import set_nonblocking, GreenSocket, SOCKET_CLOSED, CONNECT_ERR, CONNECT_SUCCESS orig_socket = __import__('socket') socket = orig_socket.socket +timeout_exc = orig_socket.timeout class GreenSSLSocket(__ssl.SSLSocket): """ This is a green version of the SSLSocket class from the ssl module added in 2.6. For documentation on it, please see the Python standard - documentation.""" + documentation. + + Python nonblocking ssl objects don't give errors when the other end + of the socket is closed (they do notice when the other end is shutdown, + though). Any write/read operations will simply hang if the socket is + closed from the other end. There is no obvious fix for this problem; + it appears to be a limitation of Python's ssl object implementation. + A workaround is to set a reasonable timeout on the socket using + settimeout(), and to close/reopen the connection when a timeout + occurs at an unexpected juncture in the code. + """ # we are inheriting from SSLSocket because its constructor calls # do_handshake whose behavior we wish to override def __init__(self, sock, *args, **kw): @@ -62,12 +73,12 @@ class GreenSSLSocket(__ssl.SSLSocket): trampoline(self.fileno(), read=True, timeout=self.gettimeout(), - timeout_exc=SSLError) + timeout_exc=timeout_exc('timed out')) elif exc[0] == SSL_ERROR_WANT_WRITE: trampoline(self.fileno(), write=True, timeout=self.gettimeout(), - timeout_exc=SSLError) + timeout_exc=timeout_exc('timed out')) else: raise @@ -121,7 +132,7 @@ class GreenSSLSocket(__ssl.SSLSocket): raise ValueError("sendto not allowed on instances of %s" % self.__class__) else: - trampoline(self.fileno(), write=True, timeout_exc=orig_socket.timeout) + trampoline(self.fileno(), write=True, timeout_exc=timeout_exc('timed out')) return socket.sendto(self, data, addr, flags) def sendall (self, data, flags=0): @@ -146,7 +157,7 @@ class GreenSSLSocket(__ssl.SSLSocket): raise if e[0] == errno.EWOULDBLOCK: trampoline(self.fileno(), write=True, - timeout=self.gettimeout(), timeout_exc=orig_socket.timeout) + timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) if e[0] in SOCKET_CLOSED: return '' raise @@ -169,7 +180,7 @@ class GreenSSLSocket(__ssl.SSLSocket): raise if e[0] == errno.EWOULDBLOCK: trampoline(self.fileno(), read=True, - timeout=self.gettimeout(), timeout_exc=orig_socket.timeout) + timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) if e[0] in SOCKET_CLOSED: return '' raise @@ -177,17 +188,17 @@ class GreenSSLSocket(__ssl.SSLSocket): def recv_into (self, buffer, nbytes=None, flags=0): if not self.act_non_blocking: - trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=orig_socket.timeout) + trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) return super(GreenSSLSocket, self).recv_into(buffer, nbytes, flags) def recvfrom (self, addr, buflen=1024, flags=0): if not self.act_non_blocking: - trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=orig_socket.timeout) + trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) return super(GreenSSLSocket, self).recvfrom(addr, buflen, flags) def recvfrom_into (self, buffer, nbytes=None, flags=0): if not self.act_non_blocking: - trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=orig_socket.timeout) + trampoline(self.fileno(), read=True, timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) return super(GreenSSLSocket, self).recvfrom_into(buffer, nbytes, flags) def unwrap(self): @@ -224,13 +235,13 @@ class GreenSSLSocket(__ssl.SSLSocket): except orig_socket.error, exc: if exc[0] in CONNECT_ERR: trampoline(self.fileno(), write=True, - timeout=end-time.time(), timeout_exc=orig_socket.timeout) + timeout=end-time.time(), timeout_exc=timeout_exc('timed out')) elif exc[0] in CONNECT_SUCCESS: return else: raise if time.time() >= end: - raise orig_socket.timeout + raise timeout_exc('timed out') def connect(self, addr): @@ -264,7 +275,7 @@ class GreenSSLSocket(__ssl.SSLSocket): if e[0] != errno.EWOULDBLOCK: raise trampoline(self.fileno(), read=True, timeout=self.gettimeout(), - timeout_exc=orig_socket.timeout) + timeout_exc=timeout_exc('timed out')) new_ssl = type(self)(newsock, keyfile=self.keyfile, @@ -276,7 +287,7 @@ class GreenSSLSocket(__ssl.SSLSocket): do_handshake_on_connect=self.do_handshake_on_connect, suppress_ragged_eofs=self.suppress_ragged_eofs) return (new_ssl, addr) - + SSLSocket = GreenSSLSocket diff --git a/eventlet/hubs/poll.py b/eventlet/hubs/poll.py index 0c72a85..c05a41c 100644 --- a/eventlet/hubs/poll.py +++ b/eventlet/hubs/poll.py @@ -7,7 +7,7 @@ import time from eventlet.hubs.hub import BaseHub, READ, WRITE EXC_MASK = select.POLLERR | select.POLLHUP -READ_MASK = select.POLLIN +READ_MASK = select.POLLIN | select.POLLPRI WRITE_MASK = select.POLLOUT class Hub(BaseHub): diff --git a/tests/stdlib/test_ssl.py b/tests/stdlib/test_ssl.py index faa4f31..b01d62b 100644 --- a/tests/stdlib/test_ssl.py +++ b/tests/stdlib/test_ssl.py @@ -30,9 +30,9 @@ patcher.inject('test.test_ssl', ('threading', threading), ('urllib', urllib)) -# these appear to not work due to some wonkiness in the threading -# module... skipping them for now (can't use SkipTest either because -# test_main doesn't understand it) +# these don't pass because nonblocking ssl sockets don't report +# when the socket is closed uncleanly, per the docstring on +# eventlet.green.GreenSSLSocket # *TODO: fix and restore these tests ThreadedTests.testProtocolSSL2 = lambda s: None ThreadedTests.testProtocolSSL3 = lambda s: None