Skip to content

Commit

Permalink
Merge pull request #396 from vstinner/py313
Browse files Browse the repository at this point in the history
Fix #392: Port to Python 3.13
  • Loading branch information
jamadden authored Sep 4, 2024
2 parents 96d63ea + 3ce88eb commit f82b66f
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 10 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
include:
- os: macos-latest
Expand All @@ -38,6 +38,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
Expand Down
28 changes: 23 additions & 5 deletions src/greenlet/TPythonState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -130,11 +134,15 @@ 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_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;
this->datastack_top = tstate->datastack_top;
this->datastack_limit = tstate->datastack_limit;
Expand All @@ -143,7 +151,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;
Expand Down Expand Up @@ -199,17 +209,25 @@ 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_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;
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;
Expand Down Expand Up @@ -238,7 +256,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;
Expand Down
4 changes: 4 additions & 0 deletions src/greenlet/greenlet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand All @@ -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 */
};

Expand Down
19 changes: 17 additions & 2 deletions src/greenlet/greenlet_cpython_compat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,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 */
Expand Down Expand Up @@ -124,4 +135,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 */
5 changes: 5 additions & 0 deletions src/greenlet/greenlet_greenlet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ using greenlet::refs::BorrowedGreenlet;
#endif

#if GREENLET_PY312
# define Py_BUILD_CORE
# include "internal/pycore_frame.h"
#endif

Expand Down Expand Up @@ -110,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;
Expand Down
4 changes: 3 additions & 1 deletion src/greenlet/tests/test_greenlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
11 changes: 10 additions & 1 deletion src/greenlet/tests/test_greenlet_trash.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,19 @@

import unittest


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 # 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):
Expand All @@ -40,7 +50,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()

assert get_tstate_trash_delete_nesting() == 0
Expand Down

0 comments on commit f82b66f

Please sign in to comment.