efed009
From beb705719827843940ecebe48ef8b8ba65d2b91c Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Wed, 9 Jun 2021 14:40:13 -0700
efed009
Subject: [PATCH 1/6] Only wrap socket.timeout on Python < 3.10
efed009
efed009
On py310, socket.timeout is TimeoutError, which our is_timeout() helper
efed009
func already knows is a timeout.
efed009
efed009
Note that this doesn't get us to py310 support (not by a long shot), but
efed009
it's a step along the way.
efed009
efed009
Closes #687
efed009
---
efed009
 eventlet/greenio/base.py | 5 ++++-
efed009
 1 file changed, 4 insertions(+), 1 deletion(-)
efed009
efed009
diff --git a/eventlet/greenio/base.py b/eventlet/greenio/base.py
efed009
index 2eed86966..51a7ae13e 100644
efed009
--- a/eventlet/greenio/base.py
efed009
+++ b/eventlet/greenio/base.py
efed009
@@ -29,7 +29,10 @@
efed009
 _original_socket = eventlet.patcher.original('socket').socket
efed009
 
efed009
 
efed009
-socket_timeout = eventlet.timeout.wrap_is_timeout(socket.timeout)
efed009
+if sys.version_info >= (3, 10):
efed009
+    socket_timeout = socket.timeout  # Really, TimeoutError
efed009
+else:
efed009
+    socket_timeout = eventlet.timeout.wrap_is_timeout(socket.timeout)
efed009
 
efed009
 
efed009
 def socket_connect(descriptor, address):
efed009
efed009
From c22a27ace90a55d14e8c60ff37f9832e76266441 Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Fri, 11 Jun 2021 12:56:07 -0700
efed009
Subject: [PATCH 2/6] Get a working greenio._open on py310
efed009
efed009
_pyio.open is now a staticmethod, which means we can't get at the
efed009
function code to wrap it up with a new environment.
efed009
efed009
Instead, create a new wrapper function which swaps out FileIO for
efed009
GreenFileIO in the function's __globals__ before calling and replaces
efed009
it in a finally.
efed009
---
efed009
 eventlet/greenio/py3.py | 30 +++++++++++++++++++++++-------
efed009
 1 file changed, 23 insertions(+), 7 deletions(-)
efed009
efed009
diff --git a/eventlet/greenio/py3.py b/eventlet/greenio/py3.py
efed009
index 7a75b52c0..c7f0aef50 100644
efed009
--- a/eventlet/greenio/py3.py
efed009
+++ b/eventlet/greenio/py3.py
efed009
@@ -2,6 +2,7 @@
efed009
 import errno
efed009
 import os as _original_os
efed009
 import socket as _original_socket
efed009
+import sys
efed009
 from io import (
efed009
     BufferedRandom as _OriginalBufferedRandom,
efed009
     BufferedReader as _OriginalBufferedReader,
efed009
@@ -19,6 +20,7 @@
efed009
     SOCKET_BLOCKING,
efed009
 )
efed009
 from eventlet.hubs import notify_close, notify_opened, IOClosed, trampoline
efed009
+from eventlet.semaphore import Semaphore
efed009
 from eventlet.support import get_errno
efed009
 import six
efed009
 
efed009
@@ -182,21 +184,35 @@ def __exit__(self, *args):
efed009
         self.close()
efed009
 
efed009
 
efed009
-_open_environment = dict(globals())
efed009
-_open_environment.update(dict(
efed009
+_open_patching = dict(
efed009
     BufferedRandom=_OriginalBufferedRandom,
efed009
     BufferedWriter=_OriginalBufferedWriter,
efed009
     BufferedReader=_OriginalBufferedReader,
efed009
     TextIOWrapper=_OriginalTextIOWrapper,
efed009
     FileIO=GreenFileIO,
efed009
     os=_original_os,
efed009
-))
efed009
-
efed009
-_open = FunctionType(
efed009
-    six.get_function_code(_original_pyio.open),
efed009
-    _open_environment,
efed009
 )
efed009
 
efed009
+if sys.version_info < (3, 10):
efed009
+    _open_environment = dict(globals())
efed009
+    _open_environment.update(_open_patching)
efed009
+    _open = FunctionType(
efed009
+        six.get_function_code(_original_pyio.open),
efed009
+        _open_environment,
efed009
+    )
efed009
+else:
efed009
+    _open_lock = Semaphore()
efed009
+    _open_originals = {k: _original_pyio.open.__func__.__globals__[k]
efed009
+                       for k in _open_patching}
efed009
+
efed009
+    def _open(*a, **kw):
efed009
+        with _open_lock:
efed009
+            try:
efed009
+                _original_pyio.open.__func__.__globals__.update(_open_patching)
efed009
+                return _original_pyio.open(*a, **kw)
efed009
+            finally:
efed009
+                _original_pyio.open.__func__.__globals__.update(_open_originals)
efed009
+
efed009
 
efed009
 def GreenPipe(name, mode="r", buffering=-1, encoding=None, errors=None,
efed009
               newline=None, closefd=True, opener=None):
efed009
efed009
From ab4f8aaa704eb56b1e15730268fffbc8724c53ea Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Fri, 11 Jun 2021 13:02:52 -0700
efed009
Subject: [PATCH 3/6] Test using eventlet.is_timeout
efed009
efed009
...rather than requiring an is_timeout attribute on errors.
efed009
efed009
TimeoutErrors (which are covered by is_timeout) can't necessarily have
efed009
attributes added to them.
efed009
---
efed009
 tests/__init__.py | 2 +-
efed009
 1 file changed, 1 insertion(+), 1 deletion(-)
efed009
efed009
diff --git a/tests/__init__.py b/tests/__init__.py
efed009
index c0b64fd9e..188366774 100644
efed009
--- a/tests/__init__.py
efed009
+++ b/tests/__init__.py
efed009
@@ -383,7 +383,7 @@ def run_isolated(path, prefix='tests/isolated/', **kwargs):
efed009
 
efed009
 def check_is_timeout(obj):
efed009
     value_text = getattr(obj, 'is_timeout', '(missing)')
efed009
-    assert obj.is_timeout, 'type={0} str={1} .is_timeout={2}'.format(type(obj), str(obj), value_text)
efed009
+    assert eventlet.is_timeout(obj), 'type={0} str={1} .is_timeout={2}'.format(type(obj), str(obj), value_text)
efed009
 
efed009
 
efed009
 @contextlib.contextmanager
efed009
efed009
From ceef941b6b730add07eff4a3d4da5d203e255a71 Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Fri, 11 Jun 2021 13:07:09 -0700
efed009
Subject: [PATCH 4/6] Fix backdoor tests on py310
efed009
efed009
Python 3.10 started including build info on the version line, so the
efed009
expectation in tests had to change. Also, start printing the banner as
efed009
we read it to aid in future debugging.
efed009
---
efed009
 tests/backdoor_test.py | 17 +++++++++++++----
efed009
 1 file changed, 13 insertions(+), 4 deletions(-)
efed009
efed009
diff --git a/tests/backdoor_test.py b/tests/backdoor_test.py
efed009
index 03a569259..67a817947 100644
efed009
--- a/tests/backdoor_test.py
efed009
+++ b/tests/backdoor_test.py
efed009
@@ -1,5 +1,6 @@
efed009
 import os
efed009
 import os.path
efed009
+import sys
efed009
 
efed009
 import eventlet
efed009
 
efed009
@@ -21,10 +22,18 @@ def test_server(self):
efed009
 
efed009
     def _run_test_on_client_and_server(self, client, server_thread):
efed009
         f = client.makefile('rw')
efed009
-        assert 'Python' in f.readline()
efed009
-        f.readline()  # build info
efed009
-        f.readline()  # help info
efed009
-        assert 'InteractiveConsole' in f.readline()
efed009
+        line = f.readline()
efed009
+        print(line.strip('\r\n'))
efed009
+        assert 'Python' in line
efed009
+        if sys.version_info < (3, 10):
efed009
+            # Starting in py310, build info is included in version line
efed009
+            line = f.readline()  # build info
efed009
+            print(line.strip('\r\n'))
efed009
+        line = f.readline()  # help info
efed009
+        print(line.strip('\r\n'))
efed009
+        line = f.readline()
efed009
+        print(line.strip('\r\n'))
efed009
+        assert 'InteractiveConsole' in line
efed009
         self.assertEqual('>>> ', f.read(4))
efed009
         f.write('print("hi")\n')
efed009
         f.flush()
efed009
efed009
From aa6720a44cda816d1ac0a183ff94ebd51b6b1e53 Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Fri, 11 Jun 2021 13:12:36 -0700
efed009
Subject: [PATCH 5/6] Tolerate __builtins__ being a dict (rather than module)
efed009
 in is_timeout
efed009
efed009
I'm still not sure how this happens, but somehow it does in
efed009
socket_test.test_error_is_timeout. As a result, is_timeout wouldn't get
efed009
a reference to TimeoutError, so the socket error would not be correctly
efed009
identified as a timeout.
efed009
---
efed009
 eventlet/timeout.py | 5 ++++-
efed009
 1 file changed, 4 insertions(+), 1 deletion(-)
efed009
efed009
diff --git a/eventlet/timeout.py b/eventlet/timeout.py
efed009
index 6e1e08f63..969fdbcbd 100644
efed009
--- a/eventlet/timeout.py
efed009
+++ b/eventlet/timeout.py
efed009
@@ -175,5 +175,8 @@ def fun(*args, **kwargs):
efed009
 
efed009
 
efed009
 def is_timeout(obj):
efed009
-    py3err = getattr(__builtins__, 'TimeoutError', Timeout)
efed009
+    if isinstance(__builtins__, dict):  # seen when running tests on py310, but HOW??
efed009
+        py3err = __builtins__.get('TimeoutError', Timeout)
efed009
+    else:
efed009
+        py3err = getattr(__builtins__, 'TimeoutError', Timeout)
efed009
     return bool(getattr(obj, 'is_timeout', False)) or isinstance(obj, py3err)
efed009
efed009
From 900c8e195154efdae526ee7901469152141ab9d2 Mon Sep 17 00:00:00 2001
efed009
From: Tim Burke <tim.burke@gmail.com>
efed009
Date: Fri, 11 Jun 2021 13:33:00 -0700
efed009
Subject: [PATCH 6/6] wsgi_test: Cap TLS version at 1.2
efed009
efed009
On py310, tests may attempt to use TLS 1.3 which apparently doesn't like
efed009
the abrupt disconnect. Instead, it would complain:
efed009
efed009
    ssl.SSLEOFError: EOF occurred in violation of protocol
efed009
---
efed009
 tests/wsgi_test.py | 3 ++-
efed009
 1 file changed, 2 insertions(+), 1 deletion(-)
efed009
efed009
diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py
efed009
index 1dd754bba..4173bcefa 100644
efed009
--- a/tests/wsgi_test.py
efed009
+++ b/tests/wsgi_test.py
efed009
@@ -579,7 +579,8 @@ def wsgi_app(environ, start_response):
efed009
         sock = eventlet.wrap_ssl(
efed009
             eventlet.listen(('localhost', 0)),
efed009
             certfile=certificate_file, keyfile=private_key_file,
efed009
-            server_side=True)
efed009
+            server_side=True,
efed009
+            ssl_version=ssl.PROTOCOL_TLSv1_2)
efed009
         server_coro = eventlet.spawn(server, sock, wsgi_app, self.logfile)
efed009
 
efed009
         client = eventlet.connect(('localhost', sock.getsockname()[1]))