Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #392: Port to Python 3.13 #396

Merged
merged 9 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading