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

Check if interpreter is finalizing in ~ThreadStateCreator #414

Merged
merged 18 commits into from
Sep 9, 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
12 changes: 9 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-latest]
include:
- os: macos-latest
python-version: 3.12
- os: macos-latest
python-version: 3.13
steps:
- uses: actions/checkout@v3
- name: Set up Python
Expand All @@ -35,6 +40,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 Expand Up @@ -90,7 +96,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: |
Expand Down
13 changes: 11 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 8 additions & 1 deletion make-manylinux
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ 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

mkdir -p /greenlet/wheelhouse
OPATH="$PATH"
which auditwheel
for variant in `ls -d /opt/python/cp{37,38,39,310,311,312}*`; do
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)"

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
17 changes: 17 additions & 0 deletions src/greenlet/TThreadStateDestroy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ 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 GREENLET_PY313
if (Py_IsFinalizing()) {
#else
if (_Py_IsFinalizing()) {
#endif
fprintf(stderr,
"greenlet: WARNING: Interpreter is finalizing. Ignoring "
"call to Py_AddPendingCall; \n");
return 0;
}
return Py_AddPendingCall(func, arg);
}
#endif
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
13 changes: 8 additions & 5 deletions src/greenlet/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -203,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,
Expand Down
Loading
Loading