Blob Blame History Raw
From beb705719827843940ecebe48ef8b8ba65d2b91c Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Wed, 9 Jun 2021 14:40:13 -0700
Subject: [PATCH 1/6] Only wrap socket.timeout on Python < 3.10

On py310, socket.timeout is TimeoutError, which our is_timeout() helper
func already knows is a timeout.

Note that this doesn't get us to py310 support (not by a long shot), but
it's a step along the way.

Closes #687
---
 eventlet/greenio/base.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/eventlet/greenio/base.py b/eventlet/greenio/base.py
index 2eed86966..51a7ae13e 100644
--- a/eventlet/greenio/base.py
+++ b/eventlet/greenio/base.py
@@ -29,7 +29,10 @@
 _original_socket = eventlet.patcher.original('socket').socket
 
 
-socket_timeout = eventlet.timeout.wrap_is_timeout(socket.timeout)
+if sys.version_info >= (3, 10):
+    socket_timeout = socket.timeout  # Really, TimeoutError
+else:
+    socket_timeout = eventlet.timeout.wrap_is_timeout(socket.timeout)
 
 
 def socket_connect(descriptor, address):

From c22a27ace90a55d14e8c60ff37f9832e76266441 Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Fri, 11 Jun 2021 12:56:07 -0700
Subject: [PATCH 2/6] Get a working greenio._open on py310

_pyio.open is now a staticmethod, which means we can't get at the
function code to wrap it up with a new environment.

Instead, create a new wrapper function which swaps out FileIO for
GreenFileIO in the function's __globals__ before calling and replaces
it in a finally.
---
 eventlet/greenio/py3.py | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/eventlet/greenio/py3.py b/eventlet/greenio/py3.py
index 7a75b52c0..c7f0aef50 100644
--- a/eventlet/greenio/py3.py
+++ b/eventlet/greenio/py3.py
@@ -2,6 +2,7 @@
 import errno
 import os as _original_os
 import socket as _original_socket
+import sys
 from io import (
     BufferedRandom as _OriginalBufferedRandom,
     BufferedReader as _OriginalBufferedReader,
@@ -19,6 +20,7 @@
     SOCKET_BLOCKING,
 )
 from eventlet.hubs import notify_close, notify_opened, IOClosed, trampoline
+from eventlet.semaphore import Semaphore
 from eventlet.support import get_errno
 import six
 
@@ -182,21 +184,35 @@ def __exit__(self, *args):
         self.close()
 
 
-_open_environment = dict(globals())
-_open_environment.update(dict(
+_open_patching = dict(
     BufferedRandom=_OriginalBufferedRandom,
     BufferedWriter=_OriginalBufferedWriter,
     BufferedReader=_OriginalBufferedReader,
     TextIOWrapper=_OriginalTextIOWrapper,
     FileIO=GreenFileIO,
     os=_original_os,
-))
-
-_open = FunctionType(
-    six.get_function_code(_original_pyio.open),
-    _open_environment,
 )
 
+if sys.version_info < (3, 10):
+    _open_environment = dict(globals())
+    _open_environment.update(_open_patching)
+    _open = FunctionType(
+        six.get_function_code(_original_pyio.open),
+        _open_environment,
+    )
+else:
+    _open_lock = Semaphore()
+    _open_originals = {k: _original_pyio.open.__func__.__globals__[k]
+                       for k in _open_patching}
+
+    def _open(*a, **kw):
+        with _open_lock:
+            try:
+                _original_pyio.open.__func__.__globals__.update(_open_patching)
+                return _original_pyio.open(*a, **kw)
+            finally:
+                _original_pyio.open.__func__.__globals__.update(_open_originals)
+
 
 def GreenPipe(name, mode="r", buffering=-1, encoding=None, errors=None,
               newline=None, closefd=True, opener=None):

From ab4f8aaa704eb56b1e15730268fffbc8724c53ea Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Fri, 11 Jun 2021 13:02:52 -0700
Subject: [PATCH 3/6] Test using eventlet.is_timeout

...rather than requiring an is_timeout attribute on errors.

TimeoutErrors (which are covered by is_timeout) can't necessarily have
attributes added to them.
---
 tests/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/__init__.py b/tests/__init__.py
index c0b64fd9e..188366774 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -383,7 +383,7 @@ def run_isolated(path, prefix='tests/isolated/', **kwargs):
 
 def check_is_timeout(obj):
     value_text = getattr(obj, 'is_timeout', '(missing)')
-    assert obj.is_timeout, 'type={0} str={1} .is_timeout={2}'.format(type(obj), str(obj), value_text)
+    assert eventlet.is_timeout(obj), 'type={0} str={1} .is_timeout={2}'.format(type(obj), str(obj), value_text)
 
 
 @contextlib.contextmanager

From ceef941b6b730add07eff4a3d4da5d203e255a71 Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Fri, 11 Jun 2021 13:07:09 -0700
Subject: [PATCH 4/6] Fix backdoor tests on py310

Python 3.10 started including build info on the version line, so the
expectation in tests had to change. Also, start printing the banner as
we read it to aid in future debugging.
---
 tests/backdoor_test.py | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/tests/backdoor_test.py b/tests/backdoor_test.py
index 03a569259..67a817947 100644
--- a/tests/backdoor_test.py
+++ b/tests/backdoor_test.py
@@ -1,5 +1,6 @@
 import os
 import os.path
+import sys
 
 import eventlet
 
@@ -21,10 +22,18 @@ def test_server(self):
 
     def _run_test_on_client_and_server(self, client, server_thread):
         f = client.makefile('rw')
-        assert 'Python' in f.readline()
-        f.readline()  # build info
-        f.readline()  # help info
-        assert 'InteractiveConsole' in f.readline()
+        line = f.readline()
+        print(line.strip('\r\n'))
+        assert 'Python' in line
+        if sys.version_info < (3, 10):
+            # Starting in py310, build info is included in version line
+            line = f.readline()  # build info
+            print(line.strip('\r\n'))
+        line = f.readline()  # help info
+        print(line.strip('\r\n'))
+        line = f.readline()
+        print(line.strip('\r\n'))
+        assert 'InteractiveConsole' in line
         self.assertEqual('>>> ', f.read(4))
         f.write('print("hi")\n')
         f.flush()

From aa6720a44cda816d1ac0a183ff94ebd51b6b1e53 Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Fri, 11 Jun 2021 13:12:36 -0700
Subject: [PATCH 5/6] Tolerate __builtins__ being a dict (rather than module)
 in is_timeout

I'm still not sure how this happens, but somehow it does in
socket_test.test_error_is_timeout. As a result, is_timeout wouldn't get
a reference to TimeoutError, so the socket error would not be correctly
identified as a timeout.
---
 eventlet/timeout.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/eventlet/timeout.py b/eventlet/timeout.py
index 6e1e08f63..969fdbcbd 100644
--- a/eventlet/timeout.py
+++ b/eventlet/timeout.py
@@ -175,5 +175,8 @@ def fun(*args, **kwargs):
 
 
 def is_timeout(obj):
-    py3err = getattr(__builtins__, 'TimeoutError', Timeout)
+    if isinstance(__builtins__, dict):  # seen when running tests on py310, but HOW??
+        py3err = __builtins__.get('TimeoutError', Timeout)
+    else:
+        py3err = getattr(__builtins__, 'TimeoutError', Timeout)
     return bool(getattr(obj, 'is_timeout', False)) or isinstance(obj, py3err)

From 900c8e195154efdae526ee7901469152141ab9d2 Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Fri, 11 Jun 2021 13:33:00 -0700
Subject: [PATCH 6/6] wsgi_test: Cap TLS version at 1.2

On py310, tests may attempt to use TLS 1.3 which apparently doesn't like
the abrupt disconnect. Instead, it would complain:

    ssl.SSLEOFError: EOF occurred in violation of protocol
---
 tests/wsgi_test.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py
index 1dd754bba..4173bcefa 100644
--- a/tests/wsgi_test.py
+++ b/tests/wsgi_test.py
@@ -579,7 +579,8 @@ def wsgi_app(environ, start_response):
         sock = eventlet.wrap_ssl(
             eventlet.listen(('localhost', 0)),
             certfile=certificate_file, keyfile=private_key_file,
-            server_side=True)
+            server_side=True,
+            ssl_version=ssl.PROTOCOL_TLSv1_2)
         server_coro = eventlet.spawn(server, sock, wsgi_app, self.logfile)
 
         client = eventlet.connect(('localhost', sock.getsockname()[1]))