Based on https://github.com/gevent/gevent/commit/8224e81425762ad21d3b63ffb9cc0a0c2d789704.patch and https://src.fedoraproject.org/rpms/python-gevent/raw/master/f/0001-code-replace.patch From e6e2959184909c6292e0135b709282cd5f96065b Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 6 Sep 2019 16:29:58 -0500 Subject: [PATCH 1/9] Basic support for CPython 3.8.0b4. Still needs the specific networking test classes added, but all the basics pass for me. Lets see about CI. --- .travis.yml | 11 +++++++ CHANGES.rst | 4 +++ examples/webproxy.py | 7 +++- pyproject.toml | 5 +-- scripts/install.sh | 3 ++ src/gevent/__semaphore.pxd | 2 +- src/gevent/_compat.py | 2 +- src/gevent/_socket2.py | 1 + src/gevent/_socketcommon.py | 12 +++++-- src/gevent/monkey.py | 46 +++++++++++++++++++++++++-- src/gevent/os.py | 29 ++++++++++++++--- src/gevent/subprocess.py | 32 +++++++++++++++++++ src/gevent/testing/testrunner.py | 17 +++++++--- src/gevent/tests/test__core_fork.py | 7 ++-- src/gevent/tests/test__threading_2.py | 26 +++++++++------ src/gevent/thread.py | 6 ++++ src/gevent/threading.py | 46 ++++++++++----------------- 17 files changed, 196 insertions(+), 60 deletions(-) #diff --git a/.travis.yml b/.travis.yml #index bf307df95..05dc24b12 100644 #--- a/.travis.yml #+++ b/.travis.yml #@@ -42,6 +42,7 @@ env: # - TRAVIS_PYTHON_VERSION=3.5 # - TRAVIS_PYTHON_VERSION=3.6 # - TRAVIS_PYTHON_VERSION=3.7 #+ - TRAVIS_PYTHON_VERSION=3.8 # - TRAVIS_PYTHON_VERSION=pypy2.7 # - TRAVIS_PYTHON_VERSION=pypy3.6 # - TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0 #@@ -144,6 +145,8 @@ jobs: # script: ccache -s # before_script: true # after_success: true #+ - <<: *build-gevent #+ env: TRAVIS_PYTHON_VERSION=3.8 # - <<: *build-gevent # env: TRAVIS_PYTHON_VERSION=3.5 # - <<: *build-gevent #@@ -278,6 +281,14 @@ jobs: # env: TRAVIS_PYTHON_VERSION=3.7 # name: pure37 # #+ # 3.8 #+ - <<: *test-libuv-jobs #+ env: TRAVIS_PYTHON_VERSION=3.8 #+ name: libuv36 #+ - <<: *test-libev-jobs #+ env: TRAVIS_PYTHON_VERSION=3.8 #+ name: libev-cffi36 #+ # # # 2.7, no-embed. Run the tests that exercise the libraries we # # linked to. #diff --git a/CHANGES.rst b/CHANGES.rst #index 52b5e1c6f..53f4398d2 100644 #--- a/CHANGES.rst #+++ b/CHANGES.rst #@@ -12,6 +12,10 @@ # - Add an ``--module`` option to ``gevent.monkey`` allowing to run a Python # module rather than a script. See :pr:`1440`. # #+- Add support for CPython 3.8.0b4. #+ #+- Improve the way joining the main thread works on Python 3. #+ # 1.5a1 (2019-05-02) # ================== # diff -urNp -x '*.orig' gevent-1.4.0.org/examples/webproxy.py gevent-1.4.0/examples/webproxy.py --- gevent-1.4.0.org/examples/webproxy.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/examples/webproxy.py 2021-03-05 09:44:15.299051013 +0100 @@ -15,7 +15,12 @@ from gevent import monkey; monkey.patch_ import sys import re import traceback -from cgi import escape + +try: + from cgi import escape +except ImportError: + # Python 3.8 removed this API + from html import escape try: import urllib2 diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/__semaphore.pxd gevent-1.4.0/src/gevent/__semaphore.pxd --- gevent-1.4.0.org/src/gevent/__semaphore.pxd 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/__semaphore.pxd 2021-03-05 09:44:15.299051013 +0100 @@ -13,7 +13,7 @@ cdef class Semaphore(AbstractLinkable): # threadpool uses it cpdef _start_notify(self) cpdef int wait(self, object timeout=*) except -1000 - cpdef bint acquire(self, int blocking=*, object timeout=*) except -1000 + cpdef bint acquire(self, bint blocking=*, object timeout=*) except -1000 cpdef __enter__(self) cpdef __exit__(self, object t, object v, object tb) diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/_compat.py gevent-1.4.0/src/gevent/_compat.py --- gevent-1.4.0.org/src/gevent/_compat.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/_compat.py 2021-03-05 09:44:15.299051013 +0100 @@ -14,7 +14,7 @@ PY3 = sys.version_info[0] >= 3 PYPY = hasattr(sys, 'pypy_version_info') WIN = sys.platform.startswith("win") LINUX = sys.platform.startswith('linux') -OSX = sys.platform == 'darwin' +OSX = MAC = sys.platform == 'darwin' PURE_PYTHON = PYPY or os.getenv('PURE_PYTHON') diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/_socket2.py gevent-1.4.0/src/gevent/_socket2.py --- gevent-1.4.0.org/src/gevent/_socket2.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/_socket2.py 2021-03-05 09:44:15.299051013 +0100 @@ -6,6 +6,7 @@ from __future__ import absolute_import # Our import magic sadly makes this warning useless # pylint: disable=undefined-variable +import sys from gevent import _socketcommon from gevent._util import copy_globals diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/_socketcommon.py gevent-1.4.0/src/gevent/_socketcommon.py --- gevent-1.4.0.org/src/gevent/_socketcommon.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/_socketcommon.py 2021-03-05 09:44:15.302384451 +0100 @@ -74,6 +74,12 @@ from gevent._hub_local import get_hub_no from gevent._compat import string_types, integer_types, PY3 from gevent._util import copy_globals +if sys.version_info[:2] >= (3, 8): + __imports__.extend([ + 'create_server', + 'has_dualstack_ipv6' + ]) + is_windows = sys.platform == 'win32' is_macos = sys.platform == 'darwin' diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/_ssl3.py gevent-1.4.0/src/gevent/_ssl3.py --- gevent-1.4.0.org/src/gevent/_ssl3.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/_ssl3.py 2021-03-05 09:44:15.299051013 +0100 @@ -667,6 +667,11 @@ class SSLSocket(socket): return None return self._sslobj.tls_unique_cb() + def verify_client_post_handshake(self): + # Only present in 3.7.1+; an attributeerror is alright + if self._sslobj: + return self._sslobj.verify_client_post_handshake() + raise ValueError("No SSL wrapper around " + str(self)) # Python does not support forward declaration of types SSLContext.sslsocket_class = SSLSocket diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/_tblib.py gevent-1.4.0/src/gevent/_tblib.py --- gevent-1.4.0.org/src/gevent/_tblib.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/_tblib.py 2021-03-05 09:44:15.302384451 +0100 @@ -198,7 +198,10 @@ class Traceback(object): while current: f_code = current.tb_frame.f_code code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec') - if PY3: + if hasattr(code, "replace"): + # Python 3.8 and newer + code = code.replace(co_argcount=0, co_freevars=(), co_cellvars=()) + elif PY3: code = CodeType( 0, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/libuv/_corecffi_source.c gevent-1.4.0/src/gevent/libuv/_corecffi_source.c --- gevent-1.4.0.org/src/gevent/libuv/_corecffi_source.c 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/libuv/_corecffi_source.c 2021-03-05 09:44:15.299051013 +0100 @@ -150,32 +150,34 @@ static void _gevent_fs_poll_callback3(vo static void gevent_uv_walk_callback_close(uv_handle_t* handle, void* arg) { - if( handle && !uv_is_closing(handle) ) { - uv_close(handle, NULL); - } + if( handle && !uv_is_closing(handle) ) { + uv_close(handle, NULL); + } } static void gevent_close_all_handles(uv_loop_t* loop) { + if (loop) { uv_walk(loop, gevent_uv_walk_callback_close, NULL); + } } static void gevent_zero_timer(uv_timer_t* handle) { - memset(handle, 0, sizeof(uv_timer_t)); + memset(handle, 0, sizeof(uv_timer_t)); } static void gevent_zero_check(uv_check_t* handle) { - memset(handle, 0, sizeof(uv_check_t)); + memset(handle, 0, sizeof(uv_check_t)); } static void gevent_zero_prepare(uv_prepare_t* handle) { - memset(handle, 0, sizeof(uv_prepare_t)); + memset(handle, 0, sizeof(uv_prepare_t)); } static void gevent_zero_loop(uv_loop_t* handle) { - memset(handle, 0, sizeof(uv_loop_t)); + memset(handle, 0, sizeof(uv_loop_t)); } diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/monkey.py gevent-1.4.0/src/gevent/monkey.py --- gevent-1.4.0.org/src/gevent/monkey.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/monkey.py 2021-03-05 09:44:15.299051013 +0100 @@ -614,6 +614,22 @@ def patch_thread(threading=True, _thread raise RuntimeError("Cannot join current thread") if thread_greenlet is not None and thread_greenlet.dead: return + # You may ask: Why not call thread_greenlet.join()? + # Well, in the one case we actually have a greenlet, it's the + # low-level greenlet.greenlet object for the main thread, which + # doesn't have a join method. + # + # You may ask: Why not become the main greenlet's *parent* + # so you can get notified when it finishes? Because you can't + # create a greenlet cycle (the current greenlet is a descendent + # of the parent), and nor can you set a greenlet's parent to None, + # so there can only ever be one greenlet with a parent of None: the main + # greenlet, the one we need to watch. + # + # You may ask: why not swizzle out the problematic lock on the main thread + # into a gevent friendly lock? Well, the interpreter actually depends on that + # for the main thread in threading._shutdown; see below. + if not thread.is_alive(): return @@ -646,8 +662,34 @@ def patch_thread(threading=True, _thread if orig_current_thread == threading_mod.main_thread(): main_thread = threading_mod.main_thread() _greenlet = main_thread._greenlet = greenlet.getcurrent() + main_thread.__real_tstate_lock = main_thread._tstate_lock + + # The interpreter will call threading._shutdown + # when the main thread exits and is about to + # go away. It is called *in* the main thread. This + # is a perfect place to notify other greenlets that + # the main thread is done. We do this by overriding the + # lock of the main thread during operation, and only restoring + # it to the native blocking version at shutdown time + # (the interpreter also has a reference to this lock in a + # C data structure). + main_thread._tstate_lock = threading_mod.Lock() + main_thread._tstate_lock.acquire() + orig_shutdown = threading_mod._shutdown + def _shutdown(): + # Release anyone trying to join() me, + # and let us switch to them. + if main_thread._tstate_lock: + main_thread._tstate_lock.release() + from gevent import sleep + sleep() + # The only truly blocking native shutdown lock to + # acquire should be our own (hopefully) + main_thread._tstate_lock = main_thread.__real_tstate_lock + main_thread.__real_tstate_lock = None + orig_shutdown() - main_thread.join = make_join_func(main_thread, _greenlet) + threading_mod._shutdown = _shutdown # Patch up the ident of the main thread to match. This # matters if threading was imported before monkey-patching diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/os.py gevent-1.4.0/src/gevent/os.py --- gevent-1.4.0.org/src/gevent/os.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/os.py 2021-03-05 09:44:15.299051013 +0100 @@ -385,6 +385,12 @@ if hasattr(os, 'fork'): # we're not watching it return _waitpid(pid, options) + def _watch_child(pid, callback=None, loop=None, ref=False): + loop = loop or get_hub().loop + watcher = loop.child(pid, ref=ref) + _watched_children[pid] = watcher + watcher.start(_on_child, watcher, callback) + def fork_and_watch(callback=None, loop=None, ref=False, fork=fork_gevent): """ Fork a child process and start a child watcher for it in the parent process. @@ -413,10 +419,7 @@ if hasattr(os, 'fork'): pid = fork() if pid: # parent - loop = loop or get_hub().loop - watcher = loop.child(pid, ref=ref) - _watched_children[pid] = watcher - watcher.start(_on_child, watcher, callback) + _watch_child(pid, callback, loop, ref) return pid __extensions__.append('fork_and_watch') @@ -474,6 +477,23 @@ if hasattr(os, 'fork'): # take any args to match fork_and_watch return forkpty_and_watch(*args, **kwargs) __implements__.append("waitpid") + + if hasattr(os, 'posix_spawn'): + _raw_posix_spawn = os.posix_spawn + _raw_posix_spawnp = os.posix_spawnp + + def posix_spawn(*args, **kwargs): + pid = _raw_posix_spawn(*args, **kwargs) + _watch_child(pid) + return pid + + def posix_spawnp(*args, **kwargs): + pid = _raw_posix_spawnp(*args, **kwargs) + _watch_child(pid) + return pid + + __implements__.append("posix_spawn") + __implements__.append("posix_spawnp") else: def fork(): """ @@ -503,6 +523,7 @@ if hasattr(os, 'fork'): else: __implements__.remove('fork') + __imports__ = copy_globals(os, globals(), names_to_ignore=__implements__ + __extensions__, dunder_names_to_keep=()) diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/testing/testrunner.py gevent-1.4.0/src/gevent/testing/testrunner.py --- gevent-1.4.0.org/src/gevent/testing/testrunner.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/testing/testrunner.py 2021-03-05 09:44:15.299051013 +0100 @@ -381,17 +381,26 @@ def _setup_environ(debug=False): if 'GEVENT_DEBUG' not in os.environ and debug: os.environ['GEVENT_DEBUG'] = 'debug' - if 'PYTHONTRACEMALLOC' not in os.environ: + if 'PYTHONTRACEMALLOC' not in os.environ and debug: + # This slows the tests down quite a bit. Reserve + # for debugging. os.environ['PYTHONTRACEMALLOC'] = '10' if 'PYTHONDEVMODE' not in os.environ: - # Python 3.7 + # Python 3.7 and above. os.environ['PYTHONDEVMODE'] = '1' - if 'PYTHONMALLOC' not in os.environ: - # Python 3.6 + if 'PYTHONMALLOC' not in os.environ and debug: + # Python 3.6 and above. + # This slows the tests down some, but + # can detect memory corruption. Unfortunately + # it can also be flaky, especially in pre-release + # versions of Python (e.g., lots of crashes on Python 3.8b4). os.environ['PYTHONMALLOC'] = 'debug' + if sys.version_info.releaselevel != 'final' and not debug: + os.environ['PYTHONMALLOC'] = 'default' + os.environ['PYTHONDEVMODE'] = '' def main(): diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/tests/known_failures.py gevent-1.4.0/src/gevent/tests/known_failures.py --- gevent-1.4.0.org/src/gevent/tests/known_failures.py 2021-03-05 09:44:15.072377256 +0100 +++ gevent-1.4.0/src/gevent/tests/known_failures.py 2021-03-05 09:44:15.302384451 +0100 @@ -610,6 +610,15 @@ RUN_ALONE = [ 'test__examples.py', ] +if APPVEYOR: + # Strange failures sometimes, but only on Python 3.7, reporting + # "ConnectionAbortedError: [WinError 10053] An established + # connection was aborted by the software in your host machine" + # when we've done no such thing. Try running not in parallel + RUN_ALONE += [ + 'test__ssl.py', + 'test__server.py', + ] if APPVEYOR or TRAVIS: diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/tests/test__core.py gevent-1.4.0/src/gevent/tests/test__core.py --- gevent-1.4.0.org/src/gevent/tests/test__core.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/tests/test__core.py 2021-03-05 09:44:15.299051013 +0100 @@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function, division import sys import unittest +import sys import gevent.testing as greentest from gevent import core @@ -130,6 +131,12 @@ class TestWatchersDefault(TestWatchers): "See https://ci.appveyor.com/project/denik/gevent/build/1.0.1380/job/lrlvid6mkjtyrhn5#L1103 " "It has also timed out, but only on Appveyor CPython 3.6; local CPython 3.6 does not. " "See https://ci.appveyor.com/project/denik/gevent/build/1.0.1414/job/yn7yi8b53vtqs8lw#L1523") +@greentest.skipIf( + greentest.LIBUV and greentest.RUNNING_ON_TRAVIS and sys.version_info == (3, 8, 0, 'beta', 4), + "Crashes on 3.8.0b4 on TravisCI. " + "(https://travis-ci.org/gevent/gevent/jobs/582031266#L215) " + "Unable to reproduce locally so far on macOS." +) class TestWatchersDefaultDestroyed(TestWatchers): def _makeOne(self): diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/tests/test__core_fork.py gevent-1.4.0/src/gevent/tests/test__core_fork.py --- gevent-1.4.0.org/src/gevent/tests/test__core_fork.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/tests/test__core_fork.py 2021-03-05 09:44:15.299051013 +0100 @@ -1,11 +1,12 @@ from __future__ import print_function import gevent.monkey gevent.monkey.patch_all() -import gevent -import os +import os import multiprocessing +import gevent + hub = gevent.get_hub() pid = os.getpid() newpid = None @@ -46,6 +47,7 @@ def test(): if __name__ == '__main__': # Must call for Windows to fork properly; the fork can't be in the top-level multiprocessing.freeze_support() + # fork watchers weren't firing in multi-threading processes. # This test is designed to prove that they are. # However, it fails on Windows: The fork watcher never runs! diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/tests/test__greenness.py gevent-1.4.0/src/gevent/tests/test__greenness.py --- gevent-1.4.0.org/src/gevent/tests/test__greenness.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/tests/test__greenness.py 2021-03-05 09:44:15.299051013 +0100 @@ -29,13 +29,15 @@ monkey.patch_all() import gevent.testing as greentest try: - import urllib2 -except ImportError: from urllib import request as urllib2 -try: - import BaseHTTPServer -except ImportError: from http import server as BaseHTTPServer + from http.server import SimpleHTTPRequestHandler +except ImportError: + # Python 2 + import urllib2 + import BaseHTTPServer + from SimpleHTTPServer import SimpleHTTPRequestHandler + import gevent from gevent.testing import params @@ -47,7 +49,8 @@ class TestGreenness(greentest.TestCase): def setUp(self): server_address = params.DEFAULT_BIND_ADDR_TUPLE BaseHTTPServer.BaseHTTPRequestHandler.protocol_version = "HTTP/1.0" - self.httpd = BaseHTTPServer.HTTPServer(server_address, BaseHTTPServer.BaseHTTPRequestHandler) + self.httpd = BaseHTTPServer.HTTPServer(server_address, + SimpleHTTPRequestHandler) self.httpd.request_count = 0 def tearDown(self): @@ -62,10 +65,10 @@ class TestGreenness(greentest.TestCase): server = gevent.spawn(self.serve) port = self.httpd.socket.getsockname()[1] - with self.assertRaises(urllib2.HTTPError) as exc: - urllib2.urlopen('http://127.0.0.1:%s' % port) - self.assertEqual(exc.exception.code, 501) - server.get(0.01) + rsp = urllib2.urlopen('http://127.0.0.1:%s' % port) + rsp.read() + rsp.close() + server.join() self.assertEqual(self.httpd.request_count, 1) diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/tests/test__threading_2.py gevent-1.4.0/src/gevent/tests/test__threading_2.py --- gevent-1.4.0.org/src/gevent/tests/test__threading_2.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/tests/test__threading_2.py 2021-03-05 09:44:15.299051013 +0100 @@ -33,6 +33,7 @@ try: from test.support import verbose except ImportError: from test.test_support import verbose + import random import re import sys @@ -46,7 +47,7 @@ import unittest import weakref from gevent.tests import lock_tests - +verbose = False # A trivial mutable counter. def skipDueToHang(cls): @@ -132,7 +133,7 @@ class ThreadTests(unittest.TestCase): print('waiting for all tasks to complete') for t in threads: t.join(NUMTASKS) - self.assertFalse(t.is_alive()) + self.assertFalse(t.is_alive(), t.__dict__) if hasattr(t, 'ident'): self.assertNotEqual(t.ident, 0) self.assertFalse(t.ident is None) @@ -351,28 +352,33 @@ class ThreadTests(unittest.TestCase): # Issue 1722344 # Raising SystemExit skipped threading._shutdown import subprocess - p = subprocess.Popen([sys.executable, "-W", "ignore", "-c", """if 1: + script = """if 1: %s import threading from time import sleep def child(): - sleep(1) + sleep(0.3) # As a non-daemon thread we SHOULD wake up and nothing # should be torn down yet - print("Woke up, sleep function is: %%r" %% sleep) + print("Woke up, sleep function is: %%s.%%s" %% (sleep.__module__, sleep.__name__)) threading.Thread(target=child).start() raise SystemExit - """ % setup_4], + """ % setup_4 + p = subprocess.Popen([sys.executable, "-W", "ignore", "-c", script], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() stdout = stdout.strip() stdout = stdout.decode('utf-8') stderr = stderr.decode('utf-8') - assert re.match('^Woke up, sleep function is: <.*?sleep.*?>$', stdout), repr(stdout) - stderr = re.sub(r"^\[\d+ refs\]", "", stderr, re.MULTILINE).strip() + + + self.assertEqual( + 'Woke up, sleep function is: gevent.hub.sleep', + stdout) + # On Python 2, importing pkg_resources tends to result in some 'ImportWarning' # being printed to stderr about packages missing __init__.py; the -W ignore is... # ignored. @@ -410,7 +416,7 @@ class ThreadTests(unittest.TestCase): self.should_raise = should_raise self.thread = threading.Thread(target=self._run, args=(self,), - kwargs={'yet_another': self}) + kwargs={'_yet_another': self}) self.thread.start() def _run(self, _other_ref, _yet_another): @@ -463,7 +469,7 @@ class ThreadJoinOnShutdown(unittest.Test t = threading.Thread(target=joiningfunc, args=(threading.current_thread(),)) t.start() - time.sleep(0.1) + time.sleep(0.2) print('end of main') """ self._run_and_join(script) diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/thread.py gevent-1.4.0/src/gevent/thread.py --- gevent-1.4.0.org/src/gevent/thread.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/thread.py 2021-03-05 09:44:15.302384451 +0100 @@ -31,6 +31,8 @@ else: 'exit_thread', 'interrupt_main', 'start_new'] + if sys.version_info[:2] >= (3, 8): + __imports__.append('get_native_id') error = __thread__.error from gevent._compat import PY3 from gevent._compat import PYPY diff -urNp -x '*.orig' gevent-1.4.0.org/src/gevent/threading.py gevent-1.4.0/src/gevent/threading.py --- gevent-1.4.0.org/src/gevent/threading.py 2019-01-04 12:51:44.000000000 +0100 +++ gevent-1.4.0/src/gevent/threading.py 2021-03-05 09:44:15.299051013 +0100 @@ -156,41 +156,25 @@ import sys if sys.version_info[:2] >= (3, 4): # XXX: Issue 18808 breaks us on Python 3.4. # Thread objects now expect a callback from the interpreter itself - # (threadmodule.c:release_sentinel). Because this never happens + # (threadmodule.c:release_sentinel) when the C-level PyThreadState + # object is being deallocated. Because this never happens # when a greenlet exits, join() and friends will block forever. - # The solution below involves capturing the greenlet when it is - # started and deferring the known broken methods to it. + # Fortunately this is easy to fix: just ensure that the allocation of the + # lock, _set_sentinel, creates a *gevent* lock, and release it when + # we're done. The main _shutdown code is in Python and deals with + # this gracefully. class Thread(__threading__.Thread): - _greenlet = None - - def is_alive(self): - return bool(self._greenlet) - - isAlive = is_alive def _set_tstate_lock(self): - self._greenlet = getcurrent() - - def run(self): - try: - super(Thread, self).run() - finally: - # avoid ref cycles, but keep in __dict__ so we can - # distinguish the started/never-started case - self._greenlet = None - self._stop() # mark as finished - - def join(self, timeout=None): - if '_greenlet' not in self.__dict__: - raise RuntimeError("Cannot join an inactive thread") - if self._greenlet is None: - return - self._greenlet.join(timeout=timeout) - - def _wait_for_tstate_lock(self, *args, **kwargs): - # pylint:disable=arguments-differ - raise NotImplementedError() + super(Thread, self)._set_tstate_lock() + greenlet = getcurrent() + greenlet.rawlink(self.__greenlet_finished) + + def __greenlet_finished(self, _): + if self._tstate_lock: + self._tstate_lock.release() + self._stop() __implements__.append('Thread') @@ -199,6 +183,8 @@ if sys.version_info[:2] >= (3, 4): __implements__.append('Timer') + _set_sentinel = allocate_lock + __implements__.append('_set_sentinel') # The main thread is patched up with more care # in _gevent_will_monkey_patch