Commit 853b8b2c authored by Jason Madden's avatar Jason Madden

Ensure that the ssl modules for Python 3 and 2.7.9+ get the gevent socket, not the stdlib socket.

If the environment had not been monkeypatched when gevent's ssl module
was imported, they could get the stdlib socket, meaning that reads and
writes could block greenlet switching for an arbitrary amount of time.
This was especially visible on SSL servers.

Fixes #597.
parent fd0828ab
...@@ -22,6 +22,9 @@ Unreleased ...@@ -22,6 +22,9 @@ Unreleased
- Additional query functions for the :mod:`gevent.monkey` module - Additional query functions for the :mod:`gevent.monkey` module
allow knowing what was patched. Discussed in :issue:`135` and allow knowing what was patched. Discussed in :issue:`135` and
implemented in :pr:`325` by Nathan Hoad. implemented in :pr:`325` by Nathan Hoad.
- In non-monkey-patched environments under Python 2.7.9 or above or
Python 3, using a gevent SSL socket could cause the greenlet to
block. See :issue:`597` by David Ford.
1.1a1 (Jun 29, 2015) 1.1a1 (Jun 29, 2015)
==================== ====================
......
...@@ -29,6 +29,9 @@ for name in dir(__ssl__): ...@@ -29,6 +29,9 @@ for name in dir(__ssl__):
continue continue
if name.startswith('__'): if name.startswith('__'):
continue continue
if name == 'socket':
# SSLSocket *must* subclass gevent.socket.socket; see issue 597
continue
value = getattr(__ssl__, name) value = getattr(__ssl__, name)
globals()[name] = value globals()[name] = value
__imports__.append(name) __imports__.append(name)
......
...@@ -36,6 +36,9 @@ for name in dir(__ssl__): ...@@ -36,6 +36,9 @@ for name in dir(__ssl__):
continue continue
if name.startswith('__'): if name.startswith('__'):
continue continue
if name == 'socket':
# SSLSocket *must* subclass gevent.socket.socket; see issue 597
continue
value = getattr(__ssl__, name) value = getattr(__ssl__, name)
globals()[name] = value globals()[name] = value
__imports__.append(name) __imports__.append(name)
......
...@@ -44,7 +44,10 @@ class Test_wsgiserver(util.TestServer): ...@@ -44,7 +44,10 @@ class Test_wsgiserver(util.TestServer):
self.assertEqual(status, '404 Not Found') self.assertEqual(status, '404 Not Found')
self.assertEqual(data, self.not_found_message) self.assertEqual(data, self.not_found_message)
def test_a_blocking_client(self): def _do_test_a_blocking_client(self):
# We spawn this in a separate server because if it's broken
# the whole server hangs
print("Checking blocking")
with self.running_server(): with self.running_server():
# First, make sure we can talk to it. # First, make sure we can talk to it.
self._test_hello() self._test_hello()
...@@ -57,7 +60,8 @@ class Test_wsgiserver(util.TestServer): ...@@ -57,7 +60,8 @@ class Test_wsgiserver(util.TestServer):
sock_file = ssl_sock.makefile(mode='rwb') sock_file = ssl_sock.makefile(mode='rwb')
else: else:
sock_file = sock.makefile(mode='rwb') sock_file = sock.makefile(mode='rwb')
sock_file.write(b'GET /xxx HTTP/1.0\r\n\r\n') # write an incomplete request
sock_file.write(b'GET /xxx HTTP/1.0\r\n')
sock_file.flush() sock_file.flush()
# Leave it open and not doing anything # Leave it open and not doing anything
# while the other request runs to completion. # while the other request runs to completion.
...@@ -65,6 +69,9 @@ class Test_wsgiserver(util.TestServer): ...@@ -65,6 +69,9 @@ class Test_wsgiserver(util.TestServer):
# doesn't hang the whole server # doesn't hang the whole server
self._test_hello() self._test_hello()
# now finish the original request
sock_file.write(b'\r\n')
sock_file.flush()
line = sock_file.readline() line = sock_file.readline()
self.assertEqual(line, b'HTTP/1.1 404 Not Found\r\n') self.assertEqual(line, b'HTTP/1.1 404 Not Found\r\n')
...@@ -73,6 +80,8 @@ class Test_wsgiserver(util.TestServer): ...@@ -73,6 +80,8 @@ class Test_wsgiserver(util.TestServer):
if ssl_sock is not None: if ssl_sock is not None:
ssl_sock.close() ssl_sock.close()
def test_a_blocking_client(self):
self._do_test_a_blocking_client()
class Test_wsgiserver_ssl(Test_wsgiserver): class Test_wsgiserver_ssl(Test_wsgiserver):
server = 'wsgiserver_ssl.py' server = 'wsgiserver_ssl.py'
...@@ -97,6 +106,10 @@ class Test_webproxy(Test_wsgiserver): ...@@ -97,6 +106,10 @@ class Test_webproxy(Test_wsgiserver):
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
assert b'google' in data.lower(), repr(data) assert b'google' in data.lower(), repr(data)
def _do_test_a_blocking_client(self):
# Not applicable
return
# class Test_webpy(Test_wsgiserver): # class Test_webpy(Test_wsgiserver):
# server = 'webpy.py' # server = 'webpy.py'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment