1Port of https://github.com/python-greenlet/greenlet/pull/396
2
3From 94979488f841fcb41bd2bd3b80b5c0b011af4c94 Mon Sep 17 00:00:00 2001
4From: Victor Stinner <vstinner@python.org>
5Date: Wed, 14 Feb 2024 16:37:42 +0100
6Subject: [PATCH 1/5] Fix #392: Port to Python 3.13
7
8* Replace C_RECURSION_LIMIT with Py_C_RECURSION_LIMIT.
9* Add Py_C_RECURSION_LIMIT for Python 3.12 and older.
10* Disable GREENLET_USE_CFRAME on Python 3.13.
11* Define Py_BUILD_CORE to include pycore_frame.h.
12---
13 src/greenlet/TPythonState.cpp | 10 +++++++---
14 src/greenlet/greenlet_cpython_compat.hpp | 13 +++++++++++--
15 src/greenlet/greenlet_greenlet.hpp | 1 +
16 3 files changed, 19 insertions(+), 5 deletions(-)
17
18diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp
19index 465d4174..c0dbf703 100644
20--- a/src/greenlet/TPythonState.cpp
21+++ b/src/greenlet/TPythonState.cpp
22@@ -130,11 +130,13 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
23 #if GREENLET_PY311
24 #if GREENLET_PY312
25 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
26- this->c_recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
27+ this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
28 #else // not 312
29 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
30 #endif // GREENLET_PY312
31+ #if GREENLET_USE_CFRAME
32 this->current_frame = tstate->cframe->current_frame;
33+ #endif
34 this->datastack_chunk = tstate->datastack_chunk;
35 this->datastack_top = tstate->datastack_top;
36 this->datastack_limit = tstate->datastack_limit;
37@@ -199,12 +201,14 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept
38 #if GREENLET_PY311
39 #if GREENLET_PY312
40 tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
41- tstate->c_recursion_remaining = C_RECURSION_LIMIT - this->c_recursion_depth;
42+ tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth;
43 this->unexpose_frames();
44 #else // \/ 3.11
45 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
46 #endif // GREENLET_PY312
47+ #if GREENLET_USE_CFRAME
48 tstate->cframe->current_frame = this->current_frame;
49+ #endif
50 tstate->datastack_chunk = this->datastack_chunk;
51 tstate->datastack_top = this->datastack_top;
52 tstate->datastack_limit = this->datastack_limit;
53@@ -238,7 +242,7 @@ void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
54 #if GREENLET_PY312
55 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
56 // XXX: TODO: Comment from a reviewer:
57- // Should this be ``C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
58+ // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
59 // But to me it looks more like that might not be the right
60 // initialization either?
61 this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
62diff --git a/src/greenlet/greenlet_cpython_compat.hpp b/src/greenlet/greenlet_cpython_compat.hpp
63index cdc1617f..c0fb94c5 100644
64--- a/src/greenlet/greenlet_cpython_compat.hpp
65+++ b/src/greenlet/greenlet_cpython_compat.hpp
66@@ -12,19 +12,24 @@
67
68 #if PY_VERSION_HEX >= 0x30A00B1
69 # define GREENLET_PY310 1
70+#else
71+# define GREENLET_PY310 0
72+#endif
73+
74 /*
75 Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member.
76 See https://github.com/python/cpython/pull/25276
77 We have to save and restore this as well.
78+
79+Python 3.13 removed PyThreadState.cframe (GH-108035).
80 */
81+#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000
82 # define GREENLET_USE_CFRAME 1
83 #else
84 # define GREENLET_USE_CFRAME 0
85-# define GREENLET_PY310 0
86 #endif
87
88
89-
90 #if PY_VERSION_HEX >= 0x30B00A4
91 /*
92 Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
93@@ -124,4 +129,8 @@ static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
94 }
95 #endif
96
97+#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT)
98+# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT
99+#endif
100+
101 #endif /* GREENLET_CPYTHON_COMPAT_H */
102diff --git a/src/greenlet/greenlet_greenlet.hpp b/src/greenlet/greenlet_greenlet.hpp
103index d52ce1fd..6da6841f 100644
104--- a/src/greenlet/greenlet_greenlet.hpp
105+++ b/src/greenlet/greenlet_greenlet.hpp
106@@ -23,6 +23,7 @@ using greenlet::refs::BorrowedGreenlet;
107 #endif
108
109 #if GREENLET_PY312
110+# define Py_BUILD_CORE
111 # include "internal/pycore_frame.h"
112 #endif
113
114
115From 00611d7567d09869973fe314f60575674cc877d8 Mon Sep 17 00:00:00 2001
116From: Victor Stinner <vstinner@python.org>
117Date: Mon, 3 Jun 2024 10:55:14 +0200
118Subject: [PATCH 3/5] Support delete_later
119
120---
121 src/greenlet/TPythonState.cpp | 14 ++++++++++++--
122 src/greenlet/greenlet.cpp | 4 ++++
123 src/greenlet/greenlet_cpython_compat.hpp | 6 ++++++
124 src/greenlet/greenlet_greenlet.hpp | 4 ++++
125 4 files changed, 26 insertions(+), 2 deletions(-)
126
127diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp
128index c0dbf703..bfb40cac 100644
129--- a/src/greenlet/TPythonState.cpp
130+++ b/src/greenlet/TPythonState.cpp
131@@ -18,7 +18,11 @@ PythonState::PythonState()
132 #else
133 ,recursion_depth(0)
134 #endif
135+#if GREENLET_PY313
136+ ,delete_later(nullptr)
137+#else
138 ,trash_delete_nesting(0)
139+#endif
140 #if GREENLET_PY311
141 ,current_frame(nullptr)
142 ,datastack_chunk(nullptr)
143@@ -145,7 +149,9 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
144 Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
145 // reference.
146 this->_top_frame.steal(frame);
147- #if GREENLET_PY312
148+ #if GREENLET_PY313
149+ this->delete_later = Py_XNewRef(tstate->delete_later);
150+ #elif GREENLET_PY312
151 this->trash_delete_nesting = tstate->trash.delete_nesting;
152 #else // not 312
153 this->trash_delete_nesting = tstate->trash_delete_nesting;
154@@ -213,7 +219,11 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept
155 tstate->datastack_top = this->datastack_top;
156 tstate->datastack_limit = this->datastack_limit;
157 this->_top_frame.relinquish_ownership();
158- #if GREENLET_PY312
159+ #if GREENLET_PY313
160+ Py_XDECREF(tstate->delete_later);
161+ tstate->delete_later = this->delete_later;
162+ Py_CLEAR(this->delete_later);
163+ #elif GREENLET_PY312
164 tstate->trash.delete_nesting = this->trash_delete_nesting;
165 #else // not 3.12
166 tstate->trash_delete_nesting = this->trash_delete_nesting;
167diff --git a/src/greenlet/greenlet.cpp b/src/greenlet/greenlet.cpp
168index 5a9818e8..dfc748a8 100644
169--- a/src/greenlet/greenlet.cpp
170+++ b/src/greenlet/greenlet.cpp
171@@ -1328,6 +1328,7 @@ mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag)
172 Py_RETURN_NONE;
173 }
174
175+#if !GREENLET_PY313
176 PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc,
177 "get_tstate_trash_delete_nesting() -> Integer\n"
178 "\n"
179@@ -1343,6 +1344,7 @@ mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module))
180 return PyLong_FromLong(tstate->trash_delete_nesting);
181 #endif
182 }
183+#endif
184
185 static PyMethodDef GreenMethods[] = {
186 {"getcurrent",
187@@ -1356,7 +1358,9 @@ static PyMethodDef GreenMethods[] = {
188 {"get_total_main_greenlets", (PyCFunction)mod_get_total_main_greenlets, METH_NOARGS, mod_get_total_main_greenlets_doc},
189 {"get_clocks_used_doing_optional_cleanup", (PyCFunction)mod_get_clocks_used_doing_optional_cleanup, METH_NOARGS, mod_get_clocks_used_doing_optional_cleanup_doc},
190 {"enable_optional_cleanup", (PyCFunction)mod_enable_optional_cleanup, METH_O, mod_enable_optional_cleanup_doc},
191+#if !GREENLET_PY313
192 {"get_tstate_trash_delete_nesting", (PyCFunction)mod_get_tstate_trash_delete_nesting, METH_NOARGS, mod_get_tstate_trash_delete_nesting_doc},
193+#endif
194 {NULL, NULL} /* Sentinel */
195 };
196
197diff --git a/src/greenlet/greenlet_cpython_compat.hpp b/src/greenlet/greenlet_cpython_compat.hpp
198index c0fb94c5..ce5fd882 100644
199--- a/src/greenlet/greenlet_cpython_compat.hpp
200+++ b/src/greenlet/greenlet_cpython_compat.hpp
201@@ -55,6 +55,12 @@ Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
202 # define GREENLET_PY312 0
203 #endif
204
205+#if PY_VERSION_HEX >= 0x30D0000
206+# define GREENLET_PY313 1
207+#else
208+# define GREENLET_PY313 0
209+#endif
210+
211 #ifndef Py_SET_REFCNT
212 /* Py_REFCNT and Py_SIZE macros are converted to functions
213 https://bugs.python.org/issue39573 */
214diff --git a/src/greenlet/greenlet_greenlet.hpp b/src/greenlet/greenlet_greenlet.hpp
215index 6da6841f..fbfdfbfc 100644
216--- a/src/greenlet/greenlet_greenlet.hpp
217+++ b/src/greenlet/greenlet_greenlet.hpp
218@@ -111,7 +111,11 @@ namespace greenlet
219 #else
220 int recursion_depth;
221 #endif
222+#if GREENLET_PY313
223+ PyObject *delete_later;
224+#else
225 int trash_delete_nesting;
226+#endif
227 #if GREENLET_PY311
228 _PyInterpreterFrame* current_frame;
229 _PyStackChunk* datastack_chunk;
230
231From b65558ec962d3d81ae09787ebca8686d233e2a4c Mon Sep 17 00:00:00 2001
232From: Victor Stinner <vstinner@python.org>
233Date: Wed, 5 Jun 2024 12:04:21 +0200
234Subject: [PATCH 4/5] Fix current_frame
235
236---
237 src/greenlet/TPythonState.cpp | 8 ++++++--
238 1 file changed, 6 insertions(+), 2 deletions(-)
239
240diff --git a/src/greenlet/TPythonState.cpp b/src/greenlet/TPythonState.cpp
241index bfb40cac..82eb34f0 100644
242--- a/src/greenlet/TPythonState.cpp
243+++ b/src/greenlet/TPythonState.cpp
244@@ -138,7 +138,9 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
245 #else // not 312
246 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
247 #endif // GREENLET_PY312
248- #if GREENLET_USE_CFRAME
249+ #if GREENLET_PY313
250+ this->current_frame = tstate->current_frame;
251+ #elif GREENLET_USE_CFRAME
252 this->current_frame = tstate->cframe->current_frame;
253 #endif
254 this->datastack_chunk = tstate->datastack_chunk;
255@@ -212,7 +214,9 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept
256 #else // \/ 3.11
257 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
258 #endif // GREENLET_PY312
259- #if GREENLET_USE_CFRAME
260+ #if GREENLET_PY313
261+ tstate->current_frame = this->current_frame;
262+ #elif GREENLET_USE_CFRAME
263 tstate->cframe->current_frame = this->current_frame;
264 #endif
265 tstate->datastack_chunk = this->datastack_chunk;
266
267From b7cfc1748766cac351fe5fca32fa7c8cacdea2ae Mon Sep 17 00:00:00 2001
268From: Victor Stinner <vstinner@python.org>
269Date: Wed, 5 Jun 2024 12:17:28 +0200
270Subject: [PATCH 5/5] Update tests
271
272---
273 src/greenlet/tests/test_greenlet.py | 4 +++-
274 src/greenlet/tests/test_greenlet_trash.py | 9 +++++++++
275 2 files changed, 12 insertions(+), 1 deletion(-)
276
277diff --git a/src/greenlet/tests/test_greenlet.py b/src/greenlet/tests/test_greenlet.py
278index 51849cd6..259707ae 100644
279--- a/src/greenlet/tests/test_greenlet.py
280+++ b/src/greenlet/tests/test_greenlet.py
281@@ -471,7 +471,9 @@ def creator():
282 # Unfortunately, this doesn't actually clear the references, they're in the
283 # fast local array.
284 if not wait_for_cleanup:
285- result[0].gr_frame.f_locals.clear()
286+ # f_locals has no clear method in Python 3.13
287+ if hasattr(result[0].gr_frame.f_locals, 'clear'):
288+ result[0].gr_frame.f_locals.clear()
289 else:
290 self.assertIsNone(result[0].gr_frame)
291
292diff --git a/src/greenlet/tests/test_greenlet_trash.py b/src/greenlet/tests/test_greenlet_trash.py
293index 8d9716e9..2bce8fd0 100644
294--- a/src/greenlet/tests/test_greenlet_trash.py
295+++ b/src/greenlet/tests/test_greenlet_trash.py
296@@ -29,8 +29,17 @@
297
298 import unittest
299
300+try:
301+ from greenlet._greenlet import get_tstate_trash_delete_nesting
302+except ImportError:
303+ get_tstate_trash_delete_nesting = None
304+
305+
306 class TestTrashCanReEnter(unittest.TestCase):
307
308+ # Python 3.13 has not "trash delete nesting" anymore (but "delete later")
309+ @unittest.skipIf(get_tstate_trash_delete_nesting is None,
310+ 'need get_tstate_trash_delete_nesting()')
311 def test_it(self):
312 # Try several times to trigger it, because it isn't 100%
313 # reliable.