From 268e229987f48602752d9e6eb8f822bcffefa0d4 Mon Sep 17 00:00:00 2001 From: Sam Schlegel Date: Wed, 26 Jun 2024 20:49:43 +0000 Subject: [PATCH 01/18] Check if the interpreter is finalizing --- src/greenlet/TThreadStateDestroy.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/greenlet/TThreadStateDestroy.cpp b/src/greenlet/TThreadStateDestroy.cpp index a149a1a4..f6d544ba 100644 --- a/src/greenlet/TThreadStateDestroy.cpp +++ b/src/greenlet/TThreadStateDestroy.cpp @@ -96,6 +96,16 @@ struct ThreadState_DestroyNoGIL // Python < 3.8 or >= 3.9 static int AddPendingCall(int (*func)(void*), void* arg) { + // If the interpreter is in the middle of finalizing, we can't add a + // pending call. Trying to do so will end up in a SIGSEGV, as + // Py_AddPendingCall will not be able to get the interpreter and will + // try to dereference a NULL pointer. It's possible this can still + // segfault if we happen to get context switched, and maybe we should + // just always implement our own AddPendingCall, but I'd like to see if + // this works first + if (_Py_IsFinalizing()) { + return 0; + } return Py_AddPendingCall(func, arg); } #endif From 69d56c1f95a1b104f24ac56872c157dc2cb851f5 Mon Sep 17 00:00:00 2001 From: Sam Schlegel Date: Wed, 26 Jun 2024 20:51:42 +0000 Subject: [PATCH 02/18] add print --- src/greenlet/TThreadStateDestroy.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/greenlet/TThreadStateDestroy.cpp b/src/greenlet/TThreadStateDestroy.cpp index f6d544ba..8c0aa15d 100644 --- a/src/greenlet/TThreadStateDestroy.cpp +++ b/src/greenlet/TThreadStateDestroy.cpp @@ -104,6 +104,9 @@ struct ThreadState_DestroyNoGIL // just always implement our own AddPendingCall, but I'd like to see if // this works first if (_Py_IsFinalizing()) { + fprintf(stderr, + "greenlet: WARNING: Interpreter is finilizing. Ignoring" + "call to Py_AddPendingCall; \n"); return 0; } return Py_AddPendingCall(func, arg); From a75d78f1547c74a81134644a179467525a255466 Mon Sep 17 00:00:00 2001 From: Sam Schlegel Date: Mon, 8 Jul 2024 12:09:07 -0700 Subject: [PATCH 03/18] spel gooder --- src/greenlet/TThreadStateDestroy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/greenlet/TThreadStateDestroy.cpp b/src/greenlet/TThreadStateDestroy.cpp index 8c0aa15d..ac6b40d6 100644 --- a/src/greenlet/TThreadStateDestroy.cpp +++ b/src/greenlet/TThreadStateDestroy.cpp @@ -105,7 +105,7 @@ struct ThreadState_DestroyNoGIL // this works first if (_Py_IsFinalizing()) { fprintf(stderr, - "greenlet: WARNING: Interpreter is finilizing. Ignoring" + "greenlet: WARNING: Interpreter is finalizing. Ignoring " "call to Py_AddPendingCall; \n"); return 0; } From 528b81680cc95a86ab25370ea3f0461384b6d753 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 14 Feb 2024 16:37:42 +0100 Subject: [PATCH 04/18] Fix #392: Port to Python 3.13 * Replace C_RECURSION_LIMIT with Py_C_RECURSION_LIMIT. * Add Py_C_RECURSION_LIMIT for Python 3.12 and older. * Disable GREENLET_USE_CFRAME on Python 3.13. * Define Py_BUILD_CORE to include pycore_frame.h. --- src/greenlet/TPythonState.cpp | 10 +++++++--- src/greenlet/greenlet_cpython_compat.hpp | 13 +++++++++++-- src/greenlet/greenlet_greenlet.hpp | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp index 465d4174..c0dbf703 100644 --- a/src/greenlet/TPythonState.cpp +++ b/src/greenlet/TPythonState.cpp @@ -130,11 +130,13 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept #if GREENLET_PY311 #if GREENLET_PY312 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; - this->c_recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining; + this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; #else // not 312 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; #endif // GREENLET_PY312 + #if GREENLET_USE_CFRAME this->current_frame = tstate->cframe->current_frame; + #endif this->datastack_chunk = tstate->datastack_chunk; this->datastack_top = tstate->datastack_top; this->datastack_limit = tstate->datastack_limit; @@ -199,12 +201,14 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept #if GREENLET_PY311 #if GREENLET_PY312 tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth; - tstate->c_recursion_remaining = C_RECURSION_LIMIT - this->c_recursion_depth; + tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth; this->unexpose_frames(); #else // \/ 3.11 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth; #endif // GREENLET_PY312 + #if GREENLET_USE_CFRAME tstate->cframe->current_frame = this->current_frame; + #endif tstate->datastack_chunk = this->datastack_chunk; tstate->datastack_top = this->datastack_top; tstate->datastack_limit = this->datastack_limit; @@ -238,7 +242,7 @@ void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept #if GREENLET_PY312 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; // XXX: TODO: Comment from a reviewer: - // Should this be ``C_RECURSION_LIMIT - tstate->c_recursion_remaining``? + // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``? // But to me it looks more like that might not be the right // initialization either? this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; diff --git a/src/greenlet/greenlet_cpython_compat.hpp b/src/greenlet/greenlet_cpython_compat.hpp index cdc1617f..c0fb94c5 100644 --- a/src/greenlet/greenlet_cpython_compat.hpp +++ b/src/greenlet/greenlet_cpython_compat.hpp @@ -12,19 +12,24 @@ #if PY_VERSION_HEX >= 0x30A00B1 # define GREENLET_PY310 1 +#else +# define GREENLET_PY310 0 +#endif + /* Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member. See https://github.com/python/cpython/pull/25276 We have to save and restore this as well. + +Python 3.13 removed PyThreadState.cframe (GH-108035). */ +#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000 # define GREENLET_USE_CFRAME 1 #else # define GREENLET_USE_CFRAME 0 -# define GREENLET_PY310 0 #endif - #if PY_VERSION_HEX >= 0x30B00A4 /* Greenlet won't compile on anything older than Python 3.11 alpha 4 (see @@ -124,4 +129,8 @@ static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) } #endif +#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT) +# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT +#endif + #endif /* GREENLET_CPYTHON_COMPAT_H */ diff --git a/src/greenlet/greenlet_greenlet.hpp b/src/greenlet/greenlet_greenlet.hpp index d52ce1fd..6da6841f 100644 --- a/src/greenlet/greenlet_greenlet.hpp +++ b/src/greenlet/greenlet_greenlet.hpp @@ -23,6 +23,7 @@ using greenlet::refs::BorrowedGreenlet; #endif #if GREENLET_PY312 +# define Py_BUILD_CORE # include "internal/pycore_frame.h" #endif From 0237e0051c6cb196c5d44061bdec533ee23c1a28 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 14 Feb 2024 16:45:56 +0100 Subject: [PATCH 05/18] Add Python 3.13 CI to GitHub Actions --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66b2e458..29e84db0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v3 @@ -35,6 +35,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: setup.py + allow-prereleases: true - name: Install dependencies run: | python -m pip install -U pip setuptools wheel From 8658e279ca5a8d6fedf1b15222b712a7c170667c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 3 Jun 2024 10:55:14 +0200 Subject: [PATCH 06/18] Support delete_later --- src/greenlet/TPythonState.cpp | 14 ++++++++++++-- src/greenlet/greenlet.cpp | 4 ++++ src/greenlet/greenlet_cpython_compat.hpp | 6 ++++++ src/greenlet/greenlet_greenlet.hpp | 4 ++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp index c0dbf703..bfb40cac 100644 --- a/src/greenlet/TPythonState.cpp +++ b/src/greenlet/TPythonState.cpp @@ -18,7 +18,11 @@ PythonState::PythonState() #else ,recursion_depth(0) #endif +#if GREENLET_PY313 + ,delete_later(nullptr) +#else ,trash_delete_nesting(0) +#endif #if GREENLET_PY311 ,current_frame(nullptr) ,datastack_chunk(nullptr) @@ -145,7 +149,9 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new // reference. this->_top_frame.steal(frame); - #if GREENLET_PY312 + #if GREENLET_PY313 + this->delete_later = Py_XNewRef(tstate->delete_later); + #elif GREENLET_PY312 this->trash_delete_nesting = tstate->trash.delete_nesting; #else // not 312 this->trash_delete_nesting = tstate->trash_delete_nesting; @@ -213,7 +219,11 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept tstate->datastack_top = this->datastack_top; tstate->datastack_limit = this->datastack_limit; this->_top_frame.relinquish_ownership(); - #if GREENLET_PY312 + #if GREENLET_PY313 + Py_XDECREF(tstate->delete_later); + tstate->delete_later = this->delete_later; + Py_CLEAR(this->delete_later); + #elif GREENLET_PY312 tstate->trash.delete_nesting = this->trash_delete_nesting; #else // not 3.12 tstate->trash_delete_nesting = this->trash_delete_nesting; diff --git a/src/greenlet/greenlet.cpp b/src/greenlet/greenlet.cpp index 5a9818e8..dfc748a8 100644 --- a/src/greenlet/greenlet.cpp +++ b/src/greenlet/greenlet.cpp @@ -1328,6 +1328,7 @@ mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag) Py_RETURN_NONE; } +#if !GREENLET_PY313 PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc, "get_tstate_trash_delete_nesting() -> Integer\n" "\n" @@ -1343,6 +1344,7 @@ mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module)) return PyLong_FromLong(tstate->trash_delete_nesting); #endif } +#endif static PyMethodDef GreenMethods[] = { {"getcurrent", @@ -1356,7 +1358,9 @@ static PyMethodDef GreenMethods[] = { {"get_total_main_greenlets", (PyCFunction)mod_get_total_main_greenlets, METH_NOARGS, mod_get_total_main_greenlets_doc}, {"get_clocks_used_doing_optional_cleanup", (PyCFunction)mod_get_clocks_used_doing_optional_cleanup, METH_NOARGS, mod_get_clocks_used_doing_optional_cleanup_doc}, {"enable_optional_cleanup", (PyCFunction)mod_enable_optional_cleanup, METH_O, mod_enable_optional_cleanup_doc}, +#if !GREENLET_PY313 {"get_tstate_trash_delete_nesting", (PyCFunction)mod_get_tstate_trash_delete_nesting, METH_NOARGS, mod_get_tstate_trash_delete_nesting_doc}, +#endif {NULL, NULL} /* Sentinel */ }; diff --git a/src/greenlet/greenlet_cpython_compat.hpp b/src/greenlet/greenlet_cpython_compat.hpp index c0fb94c5..ce5fd882 100644 --- a/src/greenlet/greenlet_cpython_compat.hpp +++ b/src/greenlet/greenlet_cpython_compat.hpp @@ -55,6 +55,12 @@ Greenlet won't compile on anything older than Python 3.11 alpha 4 (see # define GREENLET_PY312 0 #endif +#if PY_VERSION_HEX >= 0x30D0000 +# define GREENLET_PY313 1 +#else +# define GREENLET_PY313 0 +#endif + #ifndef Py_SET_REFCNT /* Py_REFCNT and Py_SIZE macros are converted to functions https://bugs.python.org/issue39573 */ diff --git a/src/greenlet/greenlet_greenlet.hpp b/src/greenlet/greenlet_greenlet.hpp index 6da6841f..fbfdfbfc 100644 --- a/src/greenlet/greenlet_greenlet.hpp +++ b/src/greenlet/greenlet_greenlet.hpp @@ -111,7 +111,11 @@ namespace greenlet #else int recursion_depth; #endif +#if GREENLET_PY313 + PyObject *delete_later; +#else int trash_delete_nesting; +#endif #if GREENLET_PY311 _PyInterpreterFrame* current_frame; _PyStackChunk* datastack_chunk; From 4cb6f221da7b97a24e986897797de9e87bf60cfe Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 12:04:21 +0200 Subject: [PATCH 07/18] Fix current_frame --- src/greenlet/TPythonState.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp index bfb40cac..82eb34f0 100644 --- a/src/greenlet/TPythonState.cpp +++ b/src/greenlet/TPythonState.cpp @@ -138,7 +138,9 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept #else // not 312 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; #endif // GREENLET_PY312 - #if GREENLET_USE_CFRAME + #if GREENLET_PY313 + this->current_frame = tstate->current_frame; + #elif GREENLET_USE_CFRAME this->current_frame = tstate->cframe->current_frame; #endif this->datastack_chunk = tstate->datastack_chunk; @@ -212,7 +214,9 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept #else // \/ 3.11 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth; #endif // GREENLET_PY312 - #if GREENLET_USE_CFRAME + #if GREENLET_PY313 + tstate->current_frame = this->current_frame; + #elif GREENLET_USE_CFRAME tstate->cframe->current_frame = this->current_frame; #endif tstate->datastack_chunk = this->datastack_chunk; From 887f2176127dbb27b905430a91df2a191eea2c5e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 12:17:28 +0200 Subject: [PATCH 08/18] Update tests --- src/greenlet/tests/test_greenlet.py | 4 +++- src/greenlet/tests/test_greenlet_trash.py | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/greenlet/tests/test_greenlet.py b/src/greenlet/tests/test_greenlet.py index 51849cd6..259707ae 100644 --- a/src/greenlet/tests/test_greenlet.py +++ b/src/greenlet/tests/test_greenlet.py @@ -471,7 +471,9 @@ def creator(): # Unfortunately, this doesn't actually clear the references, they're in the # fast local array. if not wait_for_cleanup: - result[0].gr_frame.f_locals.clear() + # f_locals has no clear method in Python 3.13 + if hasattr(result[0].gr_frame.f_locals, 'clear'): + result[0].gr_frame.f_locals.clear() else: self.assertIsNone(result[0].gr_frame) diff --git a/src/greenlet/tests/test_greenlet_trash.py b/src/greenlet/tests/test_greenlet_trash.py index 8d9716e9..2bce8fd0 100644 --- a/src/greenlet/tests/test_greenlet_trash.py +++ b/src/greenlet/tests/test_greenlet_trash.py @@ -29,8 +29,17 @@ import unittest +try: + from greenlet._greenlet import get_tstate_trash_delete_nesting +except ImportError: + get_tstate_trash_delete_nesting = None + + class TestTrashCanReEnter(unittest.TestCase): + # Python 3.13 has not "trash delete nesting" anymore (but "delete later") + @unittest.skipIf(get_tstate_trash_delete_nesting is None, + 'need get_tstate_trash_delete_nesting()') def test_it(self): # Try several times to trigger it, because it isn't 100% # reliable. From 140c06d0dacd91893cd8a9bc2f998b9e2ab0fd4e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 5 Jun 2024 12:21:54 +0200 Subject: [PATCH 09/18] Update GitHub Actions * Don't test old Python on macos-latest. GitHub Action macos-latest was updated to arm64 which only supports recent Python versions. Continue testing old Python versions on Ubuntu. * Fix make-manylinux: set /greenlet as safe directory --- .github/workflows/tests.yml | 5 ++++- make-manylinux | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29e84db0..9c873c18 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,10 @@ jobs: strategy: matrix: python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] + include: + - os: macos-latest + python: 3.12 steps: - uses: actions/checkout@v3 - name: Set up Python diff --git a/make-manylinux b/make-manylinux index c2ae7dcc..30413362 100755 --- a/make-manylinux +++ b/make-manylinux @@ -26,6 +26,7 @@ if [ -d /greenlet -a -d /opt/python ]; then # Build in an isolated directory mkdir /tmp/build cd /tmp/build + git config --global --add safe.directory /greenlet/.git git clone /greenlet greenlet cd greenlet From 8f9e7ff1c636687eddec3e9cdde6950c26175463 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Wed, 4 Sep 2024 12:20:45 -0500 Subject: [PATCH 10/18] Update test_greenlet_trash.py to avoid redefining variables --- src/greenlet/tests/test_greenlet_trash.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/greenlet/tests/test_greenlet_trash.py b/src/greenlet/tests/test_greenlet_trash.py index 2bce8fd0..a3f58b7f 100644 --- a/src/greenlet/tests/test_greenlet_trash.py +++ b/src/greenlet/tests/test_greenlet_trash.py @@ -29,18 +29,17 @@ import unittest -try: - from greenlet._greenlet import get_tstate_trash_delete_nesting -except ImportError: - get_tstate_trash_delete_nesting = None - class TestTrashCanReEnter(unittest.TestCase): - # Python 3.13 has not "trash delete nesting" anymore (but "delete later") - @unittest.skipIf(get_tstate_trash_delete_nesting is None, - 'need get_tstate_trash_delete_nesting()') def test_it(self): + try: + from greenlet._greenlet import get_tstate_trash_delete_nesting + except ImportError: + import sys + # Python 3.13 has not "trash delete nesting" anymore (but "delete later") + assert sys.version_info[:2] >= (3, 13) + self.skipTest("get_tstate_trash_delete_nesting is not available.") # Try several times to trigger it, because it isn't 100% # reliable. for _ in range(10): @@ -48,7 +47,6 @@ def test_it(self): def check_it(self): # pylint:disable=too-many-statements import greenlet - from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module main = greenlet.getcurrent() From 6fd2a57bc4f0e810d7b9d48d8ebf903b9dd720df Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Wed, 4 Sep 2024 12:27:01 -0500 Subject: [PATCH 11/18] D'oh, removed one too many. --- src/greenlet/tests/test_greenlet_trash.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/greenlet/tests/test_greenlet_trash.py b/src/greenlet/tests/test_greenlet_trash.py index a3f58b7f..c734b983 100644 --- a/src/greenlet/tests/test_greenlet_trash.py +++ b/src/greenlet/tests/test_greenlet_trash.py @@ -34,7 +34,8 @@ class TestTrashCanReEnter(unittest.TestCase): def test_it(self): try: - from greenlet._greenlet import get_tstate_trash_delete_nesting + # pylint:disable-next=no-name-in-module + from greenlet._greenlet import get_tstate_trash_delete_nesting except ImportError: import sys # Python 3.13 has not "trash delete nesting" anymore (but "delete later") @@ -47,7 +48,7 @@ def test_it(self): def check_it(self): # pylint:disable=too-many-statements import greenlet - + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module main = greenlet.getcurrent() assert get_tstate_trash_delete_nesting() == 0 From fda835d7fcb002212e6b799b2f5d37229e23494a Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Wed, 4 Sep 2024 12:42:19 -0500 Subject: [PATCH 12/18] Trailing whitespace makes the linter mad. Thanks GitHub web editor. --- src/greenlet/tests/test_greenlet_trash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/greenlet/tests/test_greenlet_trash.py b/src/greenlet/tests/test_greenlet_trash.py index c734b983..c1fc1374 100644 --- a/src/greenlet/tests/test_greenlet_trash.py +++ b/src/greenlet/tests/test_greenlet_trash.py @@ -35,12 +35,13 @@ class TestTrashCanReEnter(unittest.TestCase): def test_it(self): try: # pylint:disable-next=no-name-in-module - from greenlet._greenlet import get_tstate_trash_delete_nesting + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=unused-import except ImportError: import sys # Python 3.13 has not "trash delete nesting" anymore (but "delete later") assert sys.version_info[:2] >= (3, 13) self.skipTest("get_tstate_trash_delete_nesting is not available.") + # Try several times to trigger it, because it isn't 100% # reliable. for _ in range(10): From 0efb228722a124930bdb2e255faab7a3e933fd56 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 12:52:58 -0500 Subject: [PATCH 13/18] Add 3.13 to make-manylinux and appveyor. --- appveyor.yml | 6 ++++++ make-manylinux | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 37c3d8fd..2f33d629 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -42,6 +42,12 @@ environment: # Fully supported 64-bit versions, with testing. This should be # all the current (non EOL) versions. + - PYTHON: "C:\\Python313-x64" + PYTHON_VERSION: "3.13.0" + PYTHON_ARCH: "64" + PYTHON_EXE: python + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + - PYTHON: "C:\\Python312-x64" PYTHON_VERSION: "3.12.0" PYTHON_ARCH: "64" diff --git a/make-manylinux b/make-manylinux index 30413362..9c2d0c85 100755 --- a/make-manylinux +++ b/make-manylinux @@ -33,7 +33,7 @@ if [ -d /greenlet -a -d /opt/python ]; then mkdir -p /greenlet/wheelhouse OPATH="$PATH" which auditwheel - for variant in `ls -d /opt/python/cp{37,38,39,310,311,312}*`; do + for variant in `ls -d /opt/python/cp{313,37,38,39,310,311,312}*`; do export PATH="$variant/bin:$OPATH" echo "Building $variant $(python --version)" From a01f691bce8f98cbcfad359b866057ac35b3d44e Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 13:27:10 -0500 Subject: [PATCH 14/18] Skip a test on 3.13+manylinux. Appears to hang. It was a refcounting test so those can be flaky and version dependent anyway. --- make-manylinux | 2 ++ src/greenlet/tests/__init__.py | 12 ++++++++---- src/greenlet/tests/test_greenlet.py | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/make-manylinux b/make-manylinux index 9c2d0c85..bcfdcfed 100755 --- a/make-manylinux +++ b/make-manylinux @@ -33,6 +33,8 @@ if [ -d /greenlet -a -d /opt/python ]; then mkdir -p /greenlet/wheelhouse OPATH="$PATH" which auditwheel + echo "Installed Python versions" + ls -l /opt/python for variant in `ls -d /opt/python/cp{313,37,38,39,310,311,312}*`; do export PATH="$variant/bin:$OPATH" echo "Building $variant $(python --version)" diff --git a/src/greenlet/tests/__init__.py b/src/greenlet/tests/__init__.py index e249e351..facd8ae9 100644 --- a/src/greenlet/tests/__init__.py +++ b/src/greenlet/tests/__init__.py @@ -3,10 +3,7 @@ Tests for greenlet. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import os import sys import unittest @@ -27,7 +24,14 @@ from . import leakcheck PY312 = sys.version_info[:2] >= (3, 12) +PY313 = sys.version_info[:2] >= (3, 13) + WIN = sys.platform.startswith("win") +RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') +RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS +RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') +RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR +RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') class TestCaseMetaClass(type): # wrap each test method with diff --git a/src/greenlet/tests/test_greenlet.py b/src/greenlet/tests/test_greenlet.py index 259707ae..76651952 100644 --- a/src/greenlet/tests/test_greenlet.py +++ b/src/greenlet/tests/test_greenlet.py @@ -1,17 +1,17 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import gc import sys import time import threading +import unittest -from abc import ABCMeta, abstractmethod +from abc import ABCMeta +from abc import abstractmethod import greenlet from greenlet import greenlet as RawGreenlet from . import TestCase +from . import RUNNING_ON_MANYLINUX +from . import PY313 from .leakcheck import fails_leakcheck @@ -207,10 +207,7 @@ def run(): # we don't get the exception, it just gets printed. # When we run on 3.8 only, we can use sys.unraisablehook oldstderr = sys.stderr - try: - from cStringIO import StringIO - except ImportError: - from io import StringIO + from io import StringIO stderr = sys.stderr = StringIO() try: del g @@ -716,6 +713,13 @@ def greenlet_main(): del self.glets self.assertEqual(sys.getrefcount(Greenlet), initial_refs) + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "The manylinux images appear to hang on this test on 3.13rc2" + # Or perhaps I just got tired of waiting for the 450s timeout. + # Still, it shouldn't take anywhere near that long. Does not reproduce in + # Ubuntu images, on macOS or Windows. + ) def test_issue_245_reference_counting_subclass_threads(self): # https://github.com/python-greenlet/greenlet/issues/245 from threading import Thread @@ -1309,5 +1313,4 @@ def test_reentrant_switch_run_callable_has_del(self): ) if __name__ == '__main__': - import unittest unittest.main() From 0999eda89e55a0a86e3f45d71ad3ec5217b1c127 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 13:34:24 -0500 Subject: [PATCH 15/18] Use newer Python to run Lint. --- .github/workflows/tests.yml | 2 +- src/greenlet/tests/__init__.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c873c18..a1a966ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,7 +94,7 @@ jobs: run: | sphinx-build -b doctest -d docs/_build/doctrees2 docs docs/_build/doctest2 - name: Lint - if: matrix.python-version == '3.10' && startsWith(runner.os, 'Linux') + if: matrix.python-version == '3.12' && startsWith(runner.os, 'Linux') # We only need to do this on one version. # We do this here rather than a separate job to avoid the compilation overhead. run: | diff --git a/src/greenlet/tests/__init__.py b/src/greenlet/tests/__init__.py index facd8ae9..e69392e4 100644 --- a/src/greenlet/tests/__init__.py +++ b/src/greenlet/tests/__init__.py @@ -207,7 +207,6 @@ def get_process_uss(self): def run_script(self, script_name, show_output=True): import subprocess - import os script = os.path.join( os.path.dirname(__file__), script_name, From 32661c9ff3820caa9458a89a0a64e5baf06594a7 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 13:56:45 -0500 Subject: [PATCH 16/18] Do not try to build on no-GIL versions of Python. It wont work. --- CHANGES.rst | 13 +++++++++++-- make-manylinux | 4 ++++ src/greenlet/tests/test_greenlet.py | 7 +++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c674849a..674c9a03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,19 @@ Changes ========= -3.0.4 (unreleased) +3.1.0 (unreleased) ================== -- Nothing changed yet. +- Adds support for Python 3.13. + +.. note:: + + greenlet will not work in no-gil (free threaded) builds of CPython. + Internally, greenlet heavily depends on the GIL. + +.. note:: + + This will be the last release to support Python 3.7 and 3.8. 3.0.3 (2023-12-21) diff --git a/make-manylinux b/make-manylinux index bcfdcfed..20b74637 100755 --- a/make-manylinux +++ b/make-manylinux @@ -36,6 +36,10 @@ if [ -d /greenlet -a -d /opt/python ]; then echo "Installed Python versions" ls -l /opt/python for variant in `ls -d /opt/python/cp{313,37,38,39,310,311,312}*`; do + if [ "$variant" = "/opt/python/cp313-313t" ]; then + echo "Skipping no-gil build. The GIL is required." + continue + fi export PATH="$variant/bin:$OPATH" echo "Building $variant $(python --version)" diff --git a/src/greenlet/tests/test_greenlet.py b/src/greenlet/tests/test_greenlet.py index 76651952..456edb81 100644 --- a/src/greenlet/tests/test_greenlet.py +++ b/src/greenlet/tests/test_greenlet.py @@ -220,6 +220,13 @@ def run(): self.assertIn("SomeError", v) + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "Sometimes flaky (getting one GreenletExit in the second list)" + # Probably due to funky timing interactions? + # TODO: FIXME Make that work. + ) + def test_dealloc_other_thread(self): seen = [] someref = [] From 6f78188c4a830bb29190b39487207450faa67727 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 14:39:29 -0500 Subject: [PATCH 17/18] Try 3.13 on macos as well. --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a1a966ce..5de8f252 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,9 @@ jobs: os: [ubuntu-latest] include: - os: macos-latest - python: 3.12 + python-version: 3.12 + - os: macos-latest + python-version: 3.13 steps: - uses: actions/checkout@v3 - name: Set up Python From acdd8f151353999daeea49c26820992d6997837a Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 9 Sep 2024 15:21:46 -0500 Subject: [PATCH 18/18] On 3.13, Py_IsFinalizing is a supported API, and the private one is gone. --- src/greenlet/TThreadStateDestroy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/greenlet/TThreadStateDestroy.cpp b/src/greenlet/TThreadStateDestroy.cpp index ac6b40d6..3ad9785b 100644 --- a/src/greenlet/TThreadStateDestroy.cpp +++ b/src/greenlet/TThreadStateDestroy.cpp @@ -103,7 +103,11 @@ struct ThreadState_DestroyNoGIL // segfault if we happen to get context switched, and maybe we should // just always implement our own AddPendingCall, but I'd like to see if // this works first +#if GREENLET_PY313 + if (Py_IsFinalizing()) { +#else if (_Py_IsFinalizing()) { +#endif fprintf(stderr, "greenlet: WARNING: Interpreter is finalizing. Ignoring " "call to Py_AddPendingCall; \n");