Skip to content

Commit f82b66f

Browse files
authored
Merge pull request #396 from vstinner/py313
Fix #392: Port to Python 3.13
2 parents 96d63ea + 3ce88eb commit f82b66f

File tree

7 files changed

+64
-10
lines changed

7 files changed

+64
-10
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
runs-on: ${{ matrix.os }}
2626
strategy:
2727
matrix:
28-
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
28+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
2929
os: [ubuntu-latest]
3030
include:
3131
- os: macos-latest
@@ -38,6 +38,7 @@ jobs:
3838
python-version: ${{ matrix.python-version }}
3939
cache: 'pip'
4040
cache-dependency-path: setup.py
41+
allow-prereleases: true
4142
- name: Install dependencies
4243
run: |
4344
python -m pip install -U pip setuptools wheel

src/greenlet/TPythonState.cpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ PythonState::PythonState()
1818
#else
1919
,recursion_depth(0)
2020
#endif
21+
#if GREENLET_PY313
22+
,delete_later(nullptr)
23+
#else
2124
,trash_delete_nesting(0)
25+
#endif
2226
#if GREENLET_PY311
2327
,current_frame(nullptr)
2428
,datastack_chunk(nullptr)
@@ -130,11 +134,15 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
130134
#if GREENLET_PY311
131135
#if GREENLET_PY312
132136
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
133-
this->c_recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
137+
this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
134138
#else // not 312
135139
this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
136140
#endif // GREENLET_PY312
141+
#if GREENLET_PY313
142+
this->current_frame = tstate->current_frame;
143+
#elif GREENLET_USE_CFRAME
137144
this->current_frame = tstate->cframe->current_frame;
145+
#endif
138146
this->datastack_chunk = tstate->datastack_chunk;
139147
this->datastack_top = tstate->datastack_top;
140148
this->datastack_limit = tstate->datastack_limit;
@@ -143,7 +151,9 @@ void PythonState::operator<<(const PyThreadState *const tstate) noexcept
143151
Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
144152
// reference.
145153
this->_top_frame.steal(frame);
146-
#if GREENLET_PY312
154+
#if GREENLET_PY313
155+
this->delete_later = Py_XNewRef(tstate->delete_later);
156+
#elif GREENLET_PY312
147157
this->trash_delete_nesting = tstate->trash.delete_nesting;
148158
#else // not 312
149159
this->trash_delete_nesting = tstate->trash_delete_nesting;
@@ -199,17 +209,25 @@ void PythonState::operator>>(PyThreadState *const tstate) noexcept
199209
#if GREENLET_PY311
200210
#if GREENLET_PY312
201211
tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
202-
tstate->c_recursion_remaining = C_RECURSION_LIMIT - this->c_recursion_depth;
212+
tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth;
203213
this->unexpose_frames();
204214
#else // \/ 3.11
205215
tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
206216
#endif // GREENLET_PY312
217+
#if GREENLET_PY313
218+
tstate->current_frame = this->current_frame;
219+
#elif GREENLET_USE_CFRAME
207220
tstate->cframe->current_frame = this->current_frame;
221+
#endif
208222
tstate->datastack_chunk = this->datastack_chunk;
209223
tstate->datastack_top = this->datastack_top;
210224
tstate->datastack_limit = this->datastack_limit;
211225
this->_top_frame.relinquish_ownership();
212-
#if GREENLET_PY312
226+
#if GREENLET_PY313
227+
Py_XDECREF(tstate->delete_later);
228+
tstate->delete_later = this->delete_later;
229+
Py_CLEAR(this->delete_later);
230+
#elif GREENLET_PY312
213231
tstate->trash.delete_nesting = this->trash_delete_nesting;
214232
#else // not 3.12
215233
tstate->trash_delete_nesting = this->trash_delete_nesting;
@@ -238,7 +256,7 @@ void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
238256
#if GREENLET_PY312
239257
this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
240258
// XXX: TODO: Comment from a reviewer:
241-
// Should this be ``C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
259+
// Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
242260
// But to me it looks more like that might not be the right
243261
// initialization either?
244262
this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;

src/greenlet/greenlet.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,7 @@ mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag)
13281328
Py_RETURN_NONE;
13291329
}
13301330

1331+
#if !GREENLET_PY313
13311332
PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc,
13321333
"get_tstate_trash_delete_nesting() -> Integer\n"
13331334
"\n"
@@ -1343,6 +1344,7 @@ mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module))
13431344
return PyLong_FromLong(tstate->trash_delete_nesting);
13441345
#endif
13451346
}
1347+
#endif
13461348

13471349
static PyMethodDef GreenMethods[] = {
13481350
{"getcurrent",
@@ -1356,7 +1358,9 @@ static PyMethodDef GreenMethods[] = {
13561358
{"get_total_main_greenlets", (PyCFunction)mod_get_total_main_greenlets, METH_NOARGS, mod_get_total_main_greenlets_doc},
13571359
{"get_clocks_used_doing_optional_cleanup", (PyCFunction)mod_get_clocks_used_doing_optional_cleanup, METH_NOARGS, mod_get_clocks_used_doing_optional_cleanup_doc},
13581360
{"enable_optional_cleanup", (PyCFunction)mod_enable_optional_cleanup, METH_O, mod_enable_optional_cleanup_doc},
1361+
#if !GREENLET_PY313
13591362
{"get_tstate_trash_delete_nesting", (PyCFunction)mod_get_tstate_trash_delete_nesting, METH_NOARGS, mod_get_tstate_trash_delete_nesting_doc},
1363+
#endif
13601364
{NULL, NULL} /* Sentinel */
13611365
};
13621366

src/greenlet/greenlet_cpython_compat.hpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,24 @@
1212

1313
#if PY_VERSION_HEX >= 0x30A00B1
1414
# define GREENLET_PY310 1
15+
#else
16+
# define GREENLET_PY310 0
17+
#endif
18+
1519
/*
1620
Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member.
1721
See https://github.com/python/cpython/pull/25276
1822
We have to save and restore this as well.
23+
24+
Python 3.13 removed PyThreadState.cframe (GH-108035).
1925
*/
26+
#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000
2027
# define GREENLET_USE_CFRAME 1
2128
#else
2229
# define GREENLET_USE_CFRAME 0
23-
# define GREENLET_PY310 0
2430
#endif
2531

2632

27-
2833
#if PY_VERSION_HEX >= 0x30B00A4
2934
/*
3035
Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
@@ -50,6 +55,12 @@ Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
5055
# define GREENLET_PY312 0
5156
#endif
5257

58+
#if PY_VERSION_HEX >= 0x30D0000
59+
# define GREENLET_PY313 1
60+
#else
61+
# define GREENLET_PY313 0
62+
#endif
63+
5364
#ifndef Py_SET_REFCNT
5465
/* Py_REFCNT and Py_SIZE macros are converted to functions
5566
https://bugs.python.org/issue39573 */
@@ -124,4 +135,8 @@ static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
124135
}
125136
#endif
126137

138+
#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT)
139+
# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT
140+
#endif
141+
127142
#endif /* GREENLET_CPYTHON_COMPAT_H */

src/greenlet/greenlet_greenlet.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ using greenlet::refs::BorrowedGreenlet;
2323
#endif
2424

2525
#if GREENLET_PY312
26+
# define Py_BUILD_CORE
2627
# include "internal/pycore_frame.h"
2728
#endif
2829

@@ -110,7 +111,11 @@ namespace greenlet
110111
#else
111112
int recursion_depth;
112113
#endif
114+
#if GREENLET_PY313
115+
PyObject *delete_later;
116+
#else
113117
int trash_delete_nesting;
118+
#endif
114119
#if GREENLET_PY311
115120
_PyInterpreterFrame* current_frame;
116121
_PyStackChunk* datastack_chunk;

src/greenlet/tests/test_greenlet.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,9 @@ def creator():
471471
# Unfortunately, this doesn't actually clear the references, they're in the
472472
# fast local array.
473473
if not wait_for_cleanup:
474-
result[0].gr_frame.f_locals.clear()
474+
# f_locals has no clear method in Python 3.13
475+
if hasattr(result[0].gr_frame.f_locals, 'clear'):
476+
result[0].gr_frame.f_locals.clear()
475477
else:
476478
self.assertIsNone(result[0].gr_frame)
477479

src/greenlet/tests/test_greenlet_trash.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,19 @@
2929

3030
import unittest
3131

32+
3233
class TestTrashCanReEnter(unittest.TestCase):
3334

3435
def test_it(self):
36+
try:
37+
# pylint:disable-next=no-name-in-module
38+
from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=unused-import
39+
except ImportError:
40+
import sys
41+
# Python 3.13 has not "trash delete nesting" anymore (but "delete later")
42+
assert sys.version_info[:2] >= (3, 13)
43+
self.skipTest("get_tstate_trash_delete_nesting is not available.")
44+
3545
# Try several times to trigger it, because it isn't 100%
3646
# reliable.
3747
for _ in range(10):
@@ -40,7 +50,6 @@ def test_it(self):
4050
def check_it(self): # pylint:disable=too-many-statements
4151
import greenlet
4252
from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module
43-
4453
main = greenlet.getcurrent()
4554

4655
assert get_tstate_trash_delete_nesting() == 0

0 commit comments

Comments
 (0)