Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-greenlet for openSUSE:Factory
checked in at 2025-12-10 15:30:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-greenlet (Old)
and /work/SRC/openSUSE:Factory/.python-greenlet.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-greenlet"
Wed Dec 10 15:30:04 2025 rev:56 rq:1321788 version:3.3.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-greenlet/python-greenlet.changes
2025-09-02 17:58:07.169983962 +0200
+++
/work/SRC/openSUSE:Factory/.python-greenlet.new.1939/python-greenlet.changes
2025-12-10 15:30:55.219238144 +0100
@@ -1,0 +2,12 @@
+Tue Dec 9 11:29:59 UTC 2025 - John Paul Adrian Glaubitz
<[email protected]>
+
+- Update to 3.3.0
+ * Drop support for Python 3.9.
+ * Switch to distributing manylinux_2_28 wheels instead of manylinux2014
+ wheels. Likewise, switch from musllinux_1_1 to 1_2.
+ * Add initial support for free-threaded builds of CPython 3.14. Due to
+ limitations, we do not distribute binary wheels for free-threaded
+ CPython on Windows. (Free-threaded CPython 3.13 may work, but is
+ untested and unsupported.)
+
+-------------------------------------------------------------------
Old:
----
greenlet-3.2.4.tar.gz
New:
----
greenlet-3.3.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-greenlet.spec ++++++
--- /var/tmp/diff_new_pack.IY9hFh/_old 2025-12-10 15:30:56.983312774 +0100
+++ /var/tmp/diff_new_pack.IY9hFh/_new 2025-12-10 15:30:57.003313619 +0100
@@ -22,7 +22,7 @@
%{?sle15_python_module_pythons}
Name: python-greenlet
-Version: 3.2.4
+Version: 3.3.0
Release: 0
Summary: Lightweight in-process concurrent programming
License: MIT
++++++ greenlet-3.2.4.tar.gz -> greenlet-3.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/.github/workflows/tests.yml
new/greenlet-3.3.0/.github/workflows/tests.yml
--- old/greenlet-3.2.4/.github/workflows/tests.yml 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/.github/workflows/tests.yml 2025-12-04
15:19:40.000000000 +0100
@@ -22,14 +22,23 @@
runs-on: ${{ matrix.os }}
strategy:
matrix:
- python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14.0-rc.1"]
+ python-version:
+ - "3.10"
+ - "3.11"
+ - "3.12"
+ - "3.13"
+ - "3.14"
+ - "3.14t"
+
# Recall the macOS builds upload built wheels so all supported versions
# need to run on mac.
- os: [ubuntu-latest, macos-latest]
+ os:
+ - ubuntu-latest
+ - macos-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
@@ -42,8 +51,8 @@
- name: Install greenlet (non-Mac)
if: ${{ ! startsWith(runner.os, 'Mac') }}
run: |
- # Stupid setuptools doesn't want you running 'python setup.py' anymore,
- # but stupid pip hides all the intersting compiler output by default,
and the
+ # setuptools doesn't want you running 'python setup.py' anymore,
+ # but pip hides all the intersting compiler output by default, and the
# only way to get anything useful out is to ask *everything* to be
verbose,
# which is much more junk than we need to wade through, making it hard
to
# see what we want. What's the point of having warnings at all if we
can't
@@ -95,10 +104,24 @@
python -c 'import greenlet._greenlet as G; assert
G.GREENLET_USE_STANDARD_THREADING'
python -m unittest discover -v greenlet.tests
- name: Doctest
+ env:
+ # XXX: On 3.14t, when the thread-local bytecode cache is
+ # enabled, sphinx crashes on module cleanup: there is a
+ # reference to pdb.set_trace in ``glob``, and when the
+ # shutdown sequence tries to clear that value, it segfaults
+ # dec-reffing it after taking it out of the module dict. The
+ # object appears to be corrupt, but it is utterly unclear how
+ # we could have done this. It is 100% reliable on bath Mac and
+ # Linux. It can be traced down to a very simple doctest
+ # snippet in greenlet_gc.rst, but running that same snippet
+ # standalone in a unit test doesn't produce the error.
+ #
+ # So this is a temporary workaround.
+ PYTHON_TLBC: "0"
run: |
sphinx-build -b doctest -d docs/_build/doctrees2 docs
docs/_build/doctest2
- name: Lint
- if: matrix.python-version == '3.12' && startsWith(runner.os, 'Linux')
+ if: matrix.python-version == '3.14' && 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: |
@@ -121,9 +144,9 @@
# required for all workflows
security-events: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.10"
cache: 'pip'
@@ -150,9 +173,9 @@
name: RiscV 64
steps:
- name: checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Set up QEMU
@@ -170,22 +193,22 @@
# We use a regular Python matrix entry to share as much code as possible.
strategy:
matrix:
- python-version: [3.9]
+ python-version:
+ - "3.14"
image:
- manylinux_2_28_x86_64
- - manylinux2014_aarch64
- - manylinux2014_ppc64le
- - manylinux2014_s390x
- - manylinux2014_x86_64
- - musllinux_1_1_x86_64
- - musllinux_1_1_aarch64
+ - manylinux_2_28_aarch64
+ - manylinux_2_28_ppc64le
+ - manylinux_2_28_s390x
+ - musllinux_1_2_x86_64
+ - musllinux_1_2_aarch64
name: ${{ matrix.image }}
steps:
- name: checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Set up QEMU
@@ -202,7 +225,7 @@
path: wheelhouse/*whl
name: ${{ matrix.image }}_wheels.zip
- name: Publish package to PyPI
- uses: pypa/[email protected]
+ uses: pypa/[email protected]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
with:
user: __token__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/CHANGES.rst
new/greenlet-3.3.0/CHANGES.rst
--- old/greenlet-3.2.4/CHANGES.rst 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/CHANGES.rst 2025-12-04 15:19:40.000000000 +0100
@@ -2,6 +2,28 @@
Changes
=========
+3.3.0 (2025-12-04)
+==================
+
+- Drop support for Python 3.9.
+- Switch to distributing manylinux_2_28 wheels instead of
+ manylinux2014 wheels. Likewise, switch from musllinux_1_1 to 1_2.
+- Add initial support for free-threaded builds of CPython 3.14. Due to
+ limitations, we do not distribute binary wheels for free-threaded
+ CPython on Windows. (Free-threaded CPython 3.13 may work, but is
+ untested and unsupported.)
+
+ .. caution::
+
+ Under some rare scenarios with free-threaded 3.14, the
+ interpreter may crash on accessing a variable or attribute or
+ when shutting down. If this happens, try disabling the
+ thread-local bytecode cache. See the greenlet documentation for
+ more details. See `PR 472 by T. Wouters
+ <https://github.com/python-greenlet/greenlet/pull/472>`_ for the
+ initial free-threaded support and a discussion of the current
+ known issues.
+
3.2.4 (2025-08-07)
==================
@@ -16,7 +38,7 @@
used, and memory may leak. Also note that these configurations
are not tested by this project's CI.
- Fix an assertion error on debug builds of Python 3.14 when using the
- experimental JIT. See :issue:`460
+ experimental JIT. See `issue 460
<https://github.com/python-greenlet/greenlet/issues/460>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/PKG-INFO new/greenlet-3.3.0/PKG-INFO
--- old/greenlet-3.2.4/PKG-INFO 2025-08-07 15:13:39.288525800 +0200
+++ new/greenlet-3.3.0/PKG-INFO 2025-12-04 15:19:48.997744800 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: greenlet
-Version: 3.2.4
+Version: 3.3.0
Summary: Lightweight in-process concurrent programming
Home-page: https://greenlet.readthedocs.io/
Author: Alexey Borzenkov
@@ -21,14 +21,14 @@
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.9
+Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
License-File: LICENSE.PSF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/appveyor.yml
new/greenlet-3.3.0/appveyor.yml
--- old/greenlet-3.2.4/appveyor.yml 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/appveyor.yml 2025-12-04 15:19:40.000000000 +0100
@@ -39,8 +39,10 @@
# Fully supported 64-bit versions, with testing. This should be
# all the current (non EOL) versions.
+ # NOTE: As of 3.14.0, CPython doesn't distribute
+ # free-threaded builds, so we have no way to create those.
- PYTHON: "C:\\Python314-x64"
- PYTHON_VERSION: "3.14.0rc1"
+ PYTHON_VERSION: "3.14.0"
PYTHON_ARCH: "64"
PYTHON_EXE: python
@@ -68,24 +70,6 @@
PYTHON_EXE: python
- - PYTHON: "C:\\Python39-x64"
- PYTHON_ARCH: "64"
- PYTHON_VERSION: "3.9.x"
- PYTHON_EXE: python
-
-
-
- # Tested 32-bit versions. A small, hand-picked selection covering
- # important variations. No need to include newer versions of
- # cpython here, 32-bit x86 windows is on the way out.
-
- - PYTHON: "C:\\Python39"
- PYTHON_ARCH: "32"
- PYTHON_VERSION: "3.9.x"
- PYTHON_EXE: python
-
-
-
# Untested 64-bit versions. We don't expect any variance here from
# the other tested 64-bit versions, OR they are very EOL
@@ -137,6 +121,7 @@
& appveyor\install.ps1;
}
+ - ps: "ls \"${env:PYTHON}\""
# Prepend newly installed Python to the PATH of this build (this cannot be
# done from inside the powershell script as it would require to restart
# the parent CMD process).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/docs/caveats.rst
new/greenlet-3.3.0/docs/caveats.rst
--- old/greenlet-3.2.4/docs/caveats.rst 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/docs/caveats.rst 2025-12-04 15:19:40.000000000 +0100
@@ -33,17 +33,23 @@
See :issue:`143` for an example.
-Free-threading Is Not Supported
-===============================
+Free-threading Is Experimental
+==============================
-Beginning with 3.14 (and experimental in 3.13), CPython may be built
-in a free-threaded mode where the GIL is not used by default. greenlet
-does not support this mode (although it will build with it), and using
-greenlet in such an interpreter will cause the GIL to be enabled.
+Beginning with greenlet 3.3.0, support for Python 3.14's free-threaded
+mode is enabled. Use caution, as it has only limited testing.
-In addition, there are known issues running greenlets in a
-free-threaded CPython. These include:
+There are known issues running greenlets in a free-threaded CPython.
+These include:
+- As with any threaded program, use caution when forking. Greenlet
+ maintains internal locks and forking at the wrong time might result
+ in the child process hanging.
- Garbage collection differences may cause ``GreenletExit`` to no
longer be raised in certain multi-threaded scenarios.
- There may be other memory leaks.
+- It may be necessary to disable the thread-local bytecode cache (and
+ hence the specializing interpreter) to avoid a rare crash. If your
+ process crashes on accessing an attribute or object, or at shutdown
+ during module cleanup, try setting the environment variable
+ ``PYTHON_TLBC=0`` or using the ``-X tlbc=0`` argument.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/docs/index.rst
new/greenlet-3.3.0/docs/index.rst
--- old/greenlet-3.2.4/docs/index.rst 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/docs/index.rst 2025-12-04 15:19:40.000000000 +0100
@@ -80,8 +80,8 @@
when they execute, and since they are coroutines, many greenlets can
exist in a single native thread.
-Note that greenlet will cause a free-threaded build of Python to
-allocate a GIL, so no actual free-threading will take place. For more
+Beginning with greenlet 3.3, free-threaded builds of CPython are
+supported, but they have limited testing and some other limitations. For more
on free-threading and greenlet, see :doc:`caveats`.
.. rubric:: How are greenlets different from threads?
@@ -106,9 +106,9 @@
require fewer resources; it is often practical to have many more
greenlets than it is threads.
-Note that greenlet will cause a free-threaded build of Python to
-allocate a GIL, so no actual free-threading will take place. For more
-on free-threading and greenlet, see :doc:`caveats`.
+Beginning with greenlet 3.3, you can combine threads and greenlets on
+free-threaded builds of CPython to take advantage of the strengths of
+both, but there may be limitations. See :doc:`caveats`.
.. _race conditions: https://en.wikipedia.org/wiki/Race_condition
.. _deadlocks:
https://docs.microsoft.com/en-us/troubleshoot/dotnet/visual-basic/race-conditions-deadlocks#when-deadlocks-occur
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/make-manylinux
new/greenlet-3.3.0/make-manylinux
--- old/greenlet-3.2.4/make-manylinux 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/make-manylinux 2025-12-04 15:19:40.000000000 +0100
@@ -23,6 +23,12 @@
# It may also violate standards compliance in a few ways. Rather than
opt-out with -fno-fast-math,
# we use O3, which has neither of those problems.
export CFLAGS="-O3 -DNDEBUG -Wall"
+ # 2025-10-21: Sometime after 2025-10-01, the manylinux_ppc64le
+ # environment started shipping a version of Python 3.14 that wants to use
+ # clang++ by default. That's not installed here, and I can't get yum to
work
+ # to try to install it, but luckily g++ works just as well --- that's what
+ # everything else uses.
+ export CXX="g++"
# Build in an isolated directory
mkdir /tmp/build
cd /tmp/build
@@ -35,9 +41,9 @@
which auditwheel
echo "Installed Python versions"
ls -l /opt/python
- for variant in `ls -d /opt/python/cp{314,313,39,310,311,312}*`; do
- if [[ "$variant" == *t ]]; then
- echo "Skipping no-gil build. The GIL is required."
+ for variant in /opt/python/cp{314,313,312,311,310}*; do
+ if [[ "$variant" == *3t ]]; then
+ echo "Skipping no-gil build for 3.13; only 3.14 is fully
supported."
continue
fi
export PATH="$variant/bin:$OPATH"
@@ -46,7 +52,7 @@
python -mpip install -U pip
python -mpip install -U setuptools wheel
python -mpip wheel -v --wheel-dir ./dist .
- python -mpip install -U .[test]
+ python -mpip install -U ".[test]"
python -m unittest discover -v greenlet.tests
PATH="$OPATH" auditwheel repair dist/greenlet*.whl
cp wheelhouse/greenlet*.whl /greenlet/wheelhouse
@@ -61,5 +67,5 @@
# Mount the current directory as /greenlet
# Can't use -i on Travis with arm64, "input device not a tty"
-docker run --rm -v "$(pwd):/greenlet"
${DOCKER_IMAGE:-quay.io/pypa/manylinux2014_x86_64} /greenlet/$(basename $0)
+docker run --rm -v "$(pwd):/greenlet"
${DOCKER_IMAGE:-quay.io/pypa/manylinux_2_28_x86_64} /greenlet/$(basename $0)
ls -l wheelhouse
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/setup.py new/greenlet-3.3.0/setup.py
--- old/greenlet-3.2.4/setup.py 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/setup.py 2025-12-04 15:19:40.000000000 +0100
@@ -244,11 +244,11 @@
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries :: Python Modules'
],
@@ -263,6 +263,6 @@
'setuptools',
],
},
- python_requires=">=3.9",
+ python_requires=">=3.10",
zip_safe=False,
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/PyGreenlet.cpp
new/greenlet-3.3.0/src/greenlet/PyGreenlet.cpp
--- old/greenlet-3.2.4/src/greenlet/PyGreenlet.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/PyGreenlet.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -741,7 +741,30 @@
.tp_alloc=PyType_GenericAlloc, /* tp_alloc */
.tp_new=(newfunc)green_new, /* tp_new */
.tp_free=PyObject_GC_Del, /* tp_free */
+#ifndef Py_GIL_DISABLED
+/*
+ We may have been handling this wrong all along.
+
+ It shows as a problem with the GIL disabled. In builds of 3.14 with
+ assertions enabled, we break the garbage collector if we *ever*
+ return false from this function. The docs say this is to distinguish
+ some objects that are collectable vs some that are not, specifically
+ giving the example of PyTypeObject as the only place this is done,
+ where it distinguishes between static types like this one (allocated
+ by the C runtime at load time) and dynamic heap types (created at
+ runtime as objects). With the GIL disabled, all allocations that are
+ potentially collectable go in the mimalloc heap, and the collector
+ asserts that tp_is_gc() is true for them as it walks through the
+ heap object by object. Since we set the Py_TPFLAGS_HAS_GC bit, we
+ are always allocated in that mimalloc heap, so we must always be
+ collectable.
+
+ XXX: TODO: Could this be responsible for some apparent leaks, even
+ on GIL builds, at least in 3.14? See if we can catch an assertion
+ failure in the GC on regular 3.14 as well.
+ */
.tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
+#endif
};
#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/PyModule.cpp
new/greenlet-3.3.0/src/greenlet/PyModule.cpp
--- old/greenlet-3.2.4/src/greenlet/PyModule.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/PyModule.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -144,7 +144,7 @@
static PyObject*
mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module))
{
- std::clock_t& clocks = ThreadState::clocks_used_doing_gc();
+ std::clock_t clocks = ThreadState::clocks_used_doing_gc();
if (clocks == std::clock_t(-1)) {
Py_RETURN_NONE;
@@ -168,15 +168,15 @@
return nullptr;
}
- std::clock_t& clocks = ThreadState::clocks_used_doing_gc();
if (is_true) {
+ std::clock_t clocks = ThreadState::clocks_used_doing_gc();
// If we already have a value, we don't want to lose it.
if (clocks == std::clock_t(-1)) {
- clocks = 0;
+ ThreadState::set_clocks_used_doing_gc(0);
}
}
else {
- clocks = std::clock_t(-1);
+ ThreadState::set_clocks_used_doing_gc(std::clock_t(-1));
}
Py_RETURN_NONE;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TGreenlet.hpp
new/greenlet-3.3.0/src/greenlet/TGreenlet.hpp
--- old/greenlet-3.2.4/src/greenlet/TGreenlet.hpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TGreenlet.hpp 2025-12-04
15:19:40.000000000 +0100
@@ -34,6 +34,9 @@
#else
# include "internal/pycore_interpframe.h"
#endif
+#ifdef Py_GIL_DISABLED
+# include "internal/pycore_tstate.h"
+#endif
#endif
// XXX: TODO: Work to remove all virtual functions
@@ -122,6 +125,10 @@
// Assertion `tstate->current_executor == NULL' failed.
// see https://github.com/python-greenlet/greenlet/issues/460
PyObject* current_executor;
+ _PyStackRef* stackpointer;
+ #ifdef Py_GIL_DISABLED
+ _PyCStackRef* c_stack_refs;
+ #endif
#elif GREENLET_PY312
int py_recursion_depth;
int c_recursion_depth;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TMainGreenlet.cpp
new/greenlet-3.3.0/src/greenlet/TMainGreenlet.cpp
--- old/greenlet-3.2.4/src/greenlet/TMainGreenlet.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TMainGreenlet.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -14,11 +14,18 @@
#include "TGreenlet.hpp"
+#ifdef Py_GIL_DISABLED
+#include <atomic>
+#endif
-
-// Protected by the GIL. Incremented when we create a main greenlet,
-// in a new thread, decremented when it is destroyed.
+// Incremented when we create a main greenlet, in a new thread, decremented
+// when it is destroyed.
+#ifdef Py_GIL_DISABLED
+static std::atomic<Py_ssize_t> G_TOTAL_MAIN_GREENLETS(0);
+#else
+// Protected by the GIL.
static Py_ssize_t G_TOTAL_MAIN_GREENLETS;
+#endif
namespace greenlet {
greenlet::PythonAllocator<MainGreenlet> MainGreenlet::allocator;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TPythonState.cpp
new/greenlet-3.3.0/src/greenlet/TPythonState.cpp
--- old/greenlet-3.2.4/src/greenlet/TPythonState.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TPythonState.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -15,6 +15,10 @@
#if GREENLET_PY314
,py_recursion_depth(0)
,current_executor(nullptr)
+ ,stackpointer(nullptr)
+ #ifdef Py_GIL_DISABLED
+ ,c_stack_refs(nullptr)
+ #endif
#elif GREENLET_PY312
,py_recursion_depth(0)
,c_recursion_depth(0)
@@ -138,6 +142,9 @@
#if GREENLET_PY314
this->py_recursion_depth = tstate->py_recursion_limit -
tstate->py_recursion_remaining;
this->current_executor = tstate->current_executor;
+ #ifdef Py_GIL_DISABLED
+ this->c_stack_refs = ((_PyThreadStateImpl*)tstate)->c_stack_refs;
+ #endif
#elif GREENLET_PY312
this->py_recursion_depth = tstate->py_recursion_limit -
tstate->py_recursion_remaining;
this->c_recursion_depth = Py_C_RECURSION_LIMIT -
tstate->c_recursion_remaining;
@@ -157,6 +164,14 @@
Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
// reference.
this->_top_frame.steal(frame);
+ #if GREENLET_PY314
+ if (this->top_frame()) {
+ this->stackpointer = this->_top_frame->f_frame->stackpointer;
+ }
+ else {
+ this->stackpointer = nullptr;
+ }
+ #endif
#if GREENLET_PY313
this->delete_later = Py_XNewRef(tstate->delete_later);
#elif GREENLET_PY312
@@ -216,6 +231,9 @@
#if GREENLET_PY314
tstate->py_recursion_remaining = tstate->py_recursion_limit -
this->py_recursion_depth;
tstate->current_executor = this->current_executor;
+ #ifdef Py_GIL_DISABLED
+ ((_PyThreadStateImpl*)tstate)->c_stack_refs = this->c_stack_refs;
+ #endif
this->unexpose_frames();
#elif GREENLET_PY312
tstate->py_recursion_remaining = tstate->py_recursion_limit -
this->py_recursion_depth;
@@ -232,6 +250,11 @@
tstate->datastack_chunk = this->datastack_chunk;
tstate->datastack_top = this->datastack_top;
tstate->datastack_limit = this->datastack_limit;
+#if GREENLET_PY314 && defined(Py_GIL_DISABLED)
+ if (this->top_frame()) {
+ this->_top_frame->f_frame->stackpointer = this->stackpointer;
+ }
+#endif
this->_top_frame.relinquish_ownership();
#if GREENLET_PY313
Py_XDECREF(tstate->delete_later);
@@ -286,6 +309,16 @@
if (own_top_frame) {
Py_VISIT(this->_top_frame.borrow());
}
+#if GREENLET_PY314
+ // TODO: Should we be visiting the c_stack_refs objects?
+ // CPython uses a specific macro to do that which takes into
+ // account boxing and null values and then calls
+ // ``_PyGC_VisitStackRef``, but we don't have access to that, and
+ // we can't duplicate it ourself (because it compares
+ // ``visitproc`` to another function we can't access).
+ // The naive way of looping over c_stack_refs->ref and visiting
+ // those crashes the process (at least with GIL disabled).
+#endif
return 0;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TThreadState.hpp
new/greenlet-3.3.0/src/greenlet/TThreadState.hpp
--- old/greenlet-3.2.4/src/greenlet/TThreadState.hpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TThreadState.hpp 2025-12-04
15:19:40.000000000 +0100
@@ -3,6 +3,7 @@
#include <ctime>
#include <stdexcept>
+#include <atomic>
#include "greenlet_internal.hpp"
#include "greenlet_refs.hpp"
@@ -65,18 +66,13 @@
* invoke destructors; the C++11 version is the most portable solution
* I found). When the thread exits, we can drop references and
* otherwise manipulate greenlets and frames that we know can no
- * longer be switched to. For compilers that don't support C++11
- * thread locals, we have a solution that uses the python thread
- * dictionary, though it may not collect everything as promptly as
- * other compilers do, if some other library is using the thread
- * dictionary and has a cycle or extra reference.
+ * longer be switched to.
*
* There are two small wrinkles. The first is that when the thread
* exits, it is too late to actually invoke Python APIs: the Python
* thread state is gone, and the GIL is released. To solve *this*
* problem, our destructor uses ``Py_AddPendingCall`` to transfer the
- * destruction work to the main thread. (This is not an issue for the
- * dictionary solution.)
+ * destruction work to the main thread.
*
* The second is that once the thread exits, the thread local object
* is invalid and we can't even access a pointer to it, so we can't
@@ -118,7 +114,11 @@
void* exception_state;
#endif
+#ifdef Py_GIL_DISABLED
+ static std::atomic<std::clock_t> _clocks_used_doing_gc;
+#else
static std::clock_t _clocks_used_doing_gc;
+#endif
static ImmortalString get_referrers_name;
static PythonAllocator<ThreadState> allocator;
@@ -160,7 +160,7 @@
static void init()
{
ThreadState::get_referrers_name = "get_referrers";
- ThreadState::_clocks_used_doing_gc = 0;
+ ThreadState::set_clocks_used_doing_gc(0);
}
ThreadState()
@@ -349,9 +349,31 @@
/**
* Set to std::clock_t(-1) to disable.
*/
- inline static std::clock_t& clocks_used_doing_gc()
+ inline static std::clock_t clocks_used_doing_gc()
{
+#ifdef Py_GIL_DISABLED
+ return
ThreadState::_clocks_used_doing_gc.load(std::memory_order_relaxed);
+#else
return ThreadState::_clocks_used_doing_gc;
+#endif
+ }
+
+ inline static void set_clocks_used_doing_gc(std::clock_t value)
+ {
+#ifdef Py_GIL_DISABLED
+ ThreadState::_clocks_used_doing_gc.store(value,
std::memory_order_relaxed);
+#else
+ ThreadState::_clocks_used_doing_gc = value;
+#endif
+ }
+
+ inline static void add_clocks_used_doing_gc(std::clock_t value)
+ {
+#ifdef Py_GIL_DISABLED
+ ThreadState::_clocks_used_doing_gc.fetch_add(value,
std::memory_order_relaxed);
+#else
+ ThreadState::_clocks_used_doing_gc += value;
+#endif
}
~ThreadState()
@@ -390,7 +412,7 @@
PyGreenlet* old_main_greenlet = this->main_greenlet.borrow();
Py_ssize_t cnt = this->main_greenlet.REFCNT();
this->main_greenlet.CLEAR();
- if (ThreadState::_clocks_used_doing_gc != std::clock_t(-1)
+ if (ThreadState::clocks_used_doing_gc() != std::clock_t(-1)
&& cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) {
// Highly likely that the reference is somewhere on
// the stack, not reachable by GC. Verify.
@@ -444,7 +466,7 @@
}
}
std::clock_t end = std::clock();
- ThreadState::_clocks_used_doing_gc += (end - begin);
+ ThreadState::add_clocks_used_doing_gc(end - begin);
}
}
}
@@ -486,7 +508,11 @@
ImmortalString ThreadState::get_referrers_name(nullptr);
PythonAllocator<ThreadState> ThreadState::allocator;
+#ifdef Py_GIL_DISABLED
+std::atomic<std::clock_t> ThreadState::_clocks_used_doing_gc(0);
+#else
std::clock_t ThreadState::_clocks_used_doing_gc(0);
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TThreadStateCreator.hpp
new/greenlet-3.3.0/src/greenlet/TThreadStateCreator.hpp
--- old/greenlet-3.2.4/src/greenlet/TThreadStateCreator.hpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TThreadStateCreator.hpp 2025-12-04
15:19:40.000000000 +0100
@@ -15,6 +15,8 @@
typedef void (*ThreadStateDestructor)(ThreadState* const);
+// Only one of these, auto created per thread as a thread_local.
+// Constructing the state constructs the MainGreenlet.
template<ThreadStateDestructor Destructor>
class ThreadStateCreator
{
@@ -36,8 +38,6 @@
public:
- // Only one of these, auto created per thread.
- // Constructing the state constructs the MainGreenlet.
ThreadStateCreator() :
_state((ThreadState*)1)
{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/TThreadStateDestroy.cpp
new/greenlet-3.3.0/src/greenlet/TThreadStateDestroy.cpp
--- old/greenlet-3.2.4/src/greenlet/TThreadStateDestroy.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/TThreadStateDestroy.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -33,6 +33,7 @@
MarkGreenletDeadAndQueueCleanup(ThreadState* const state)
{
#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK
+ // One rare platform.
return;
#endif
// We are *NOT* holding the GIL. Our thread is in the middle
@@ -70,7 +71,11 @@
static bool
MarkGreenletDeadIfNeeded(ThreadState* const state)
{
- if (state && state->has_main_greenlet()) {
+ if (!state) {
+ return false;
+ }
+ LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
+ if (state->has_main_greenlet()) {
// mark the thread as dead ASAP.
// this is racy! If we try to throw or switch to a
// greenlet from this thread from some other thread before
@@ -114,7 +119,7 @@
// from the queue; its next iteration will go ahead and delete the
item we just added.
// And the pending call we schedule here will have no work to do.
int result = AddPendingCall(
- PendingCallback_DestroyQueueWithGIL,
+ PendingCallback_DestroyQueue,
nullptr);
if (result < 0) {
// Hmm, what can we do here?
@@ -126,10 +131,11 @@
}
static int
- PendingCallback_DestroyQueueWithGIL(void* UNUSED(arg))
+ PendingCallback_DestroyQueue(void* UNUSED(arg))
{
- // We're holding the GIL here, so no Python code should be able to
- // run to call ``os.fork()``.
+ // We're may or may not be holding the GIL here (depending on
+ // Py_GIL_DISABLED), so calls to ``os.fork()`` may or may not
+ // be possible.
while (1) {
ThreadState* to_destroy;
{
@@ -144,15 +150,15 @@
// Drop the lock while we do the actual deletion.
// This allows other calls to MarkGreenletDeadAndQueueCleanup
// to enter and add to our queue.
- DestroyOneWithGIL(to_destroy);
+ DestroyOne(to_destroy);
}
return 0;
}
static void
- DestroyOneWithGIL(const ThreadState* const state)
+ DestroyOne(const ThreadState* const state)
{
- // Holding the GIL.
+ // May or may not be holding the GIL (depending on Py_GIL_DISABLED).
// Passed a non-shared pointer to the actual thread state.
// state -> main greenlet
assert(state->has_main_greenlet());
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/__init__.py
new/greenlet-3.3.0/src/greenlet/__init__.py
--- old/greenlet-3.2.4/src/greenlet/__init__.py 2025-08-07 15:13:36.000000000
+0200
+++ new/greenlet-3.3.0/src/greenlet/__init__.py 2025-12-04 15:19:40.000000000
+0100
@@ -25,7 +25,7 @@
###
# Metadata
###
-__version__ = '3.2.4'
+__version__ = '3.3.0'
from ._greenlet import _C_API # pylint:disable=no-name-in-module
###
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/greenlet.cpp
new/greenlet-3.3.0/src/greenlet/greenlet.cpp
--- old/greenlet-3.2.4/src/greenlet/greenlet.cpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/greenlet.cpp 2025-12-04
15:19:40.000000000 +0100
@@ -291,6 +291,9 @@
// << "\n\tPyGreenlet : " << sizeof(PyGreenlet)
// << endl;
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m.borrow(), Py_MOD_GIL_NOT_USED);
+#endif
return m.borrow(); // But really it's the main reference.
}
catch (const LockInitError& e) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/greenlet_allocator.hpp
new/greenlet-3.3.0/src/greenlet/greenlet_allocator.hpp
--- old/greenlet-3.2.4/src/greenlet/greenlet_allocator.hpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/greenlet_allocator.hpp 2025-12-04
15:19:40.000000000 +0100
@@ -10,30 +10,6 @@
namespace greenlet
{
-#if defined(Py_GIL_DISABLED)
-// Python on free threaded builds says this
-//
(https://docs.python.org/3/howto/free-threading-extensions.html#memory-allocation-apis):
-//
-// For thread-safety, the free-threaded build requires that only
-// Python objects are allocated using the object domain, and that all
-// Python object are allocated using that domain.
-//
-// This turns out to be important because the GC implementation on
-// free threaded Python uses internal mimalloc APIs to find allocated
-// objects. If we allocate non-PyObject objects using that API, then
-// Bad Things could happen, including crashes and improper results.
-// So in that case, we revert to standard C++ allocation.
-
- template <class T>
- struct PythonAllocator : public std::allocator<T> {
- // This member is deprecated in C++17 and removed in C++20
- template< class U >
- struct rebind {
- typedef PythonAllocator<U> other;
- };
- };
-
-#else
// This allocator is stateless; all instances are identical.
// It can *ONLY* be used when we're sure we're holding the GIL
// (Python's allocators require the GIL).
@@ -60,10 +36,16 @@
T* allocate(size_t number_objects, const void* UNUSED(hint)=0)
{
void* p;
- if (number_objects == 1)
+ if (number_objects == 1) {
+#ifdef Py_GIL_DISABLED
+ p = PyMem_Malloc(sizeof(T) * number_objects);
+#else
p = PyObject_Malloc(sizeof(T));
- else
+#endif
+ }
+ else {
p = PyMem_Malloc(sizeof(T) * number_objects);
+ }
return static_cast<T*>(p);
}
@@ -71,10 +53,15 @@
{
void* p = t;
if (n == 1) {
+#ifdef Py_GIL_DISABLED
+ PyMem_Free(p);
+#else
PyObject_Free(p);
+#endif
}
- else
+ else {
PyMem_Free(p);
+ }
}
// This member is deprecated in C++17 and removed in C++20
template< class U >
@@ -83,7 +70,7 @@
};
};
-#endif // allocator type
+
}
#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/greenlet_slp_switch.hpp
new/greenlet-3.3.0/src/greenlet/greenlet_slp_switch.hpp
--- old/greenlet-3.2.4/src/greenlet/greenlet_slp_switch.hpp 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/greenlet_slp_switch.hpp 2025-12-04
15:19:40.000000000 +0100
@@ -36,7 +36,11 @@
// running this code, the thread isn't exiting. This also nets us a
// 10-12% speed improvement.
+#if Py_GIL_DISABLED
+thread_local greenlet::Greenlet* switching_thread_state = nullptr;
+#else
static greenlet::Greenlet* volatile switching_thread_state = nullptr;
+#endif
extern "C" {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/tests/_test_extension.c
new/greenlet-3.3.0/src/greenlet/tests/_test_extension.c
--- old/greenlet-3.2.4/src/greenlet/tests/_test_extension.c 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/tests/_test_extension.c 2025-12-04
15:19:40.000000000 +0100
@@ -10,8 +10,32 @@
#define TEST_MODULE_NAME "_test_extension"
+// CAUTION: MSVC is stupidly picky:
+//
+// "The compiler ignores, without warning, any __declspec keywords
+// placed after * or & and in front of the variable identifier in a
+// declaration."
+// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160)
+//
+// So pointer return types must be handled differently (because of the
+// trailing *), or you get inscrutable compiler warnings like "error
+// C2059: syntax error: ''"
+//
+// In C23, there is a standard syntax for attributes, and
+// GCC defines an attribute to use with this: [[gnu:noinline]].
+// In the future, this is expected to become standard.
+
+#if defined(__GNUC__) || defined(__clang__)
+/* We used to check for GCC 4+ or 3.4+, but those compilers are
+ laughably out of date. Just assume they support it. */
+# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
+#elif defined(_MSC_VER)
+/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */
+# define UNUSED(x) UNUSED_ ## x
+#endif
+
static PyObject*
-test_switch(PyObject* self, PyObject* greenlet)
+test_switch(PyObject* UNUSED(self), PyObject* greenlet)
{
PyObject* result = NULL;
@@ -33,7 +57,7 @@
}
static PyObject*
-test_switch_kwargs(PyObject* self, PyObject* args, PyObject* kwargs)
+test_switch_kwargs(PyObject* UNUSED(self), PyObject* args, PyObject* kwargs)
{
PyGreenlet* g = NULL;
PyObject* result = NULL;
@@ -58,7 +82,7 @@
}
static PyObject*
-test_getcurrent(PyObject* self)
+test_getcurrent(PyObject* UNUSED(self))
{
PyGreenlet* g = PyGreenlet_GetCurrent();
if (g == NULL || !PyGreenlet_Check(g) || !PyGreenlet_ACTIVE(g)) {
@@ -72,7 +96,7 @@
}
static PyObject*
-test_setparent(PyObject* self, PyObject* arg)
+test_setparent(PyObject* UNUSED(self), PyObject* arg)
{
PyGreenlet* current;
PyGreenlet* greenlet = NULL;
@@ -97,7 +121,7 @@
}
static PyObject*
-test_new_greenlet(PyObject* self, PyObject* callable)
+test_new_greenlet(PyObject* UNUSED(self), PyObject* callable)
{
PyObject* result = NULL;
PyGreenlet* greenlet = PyGreenlet_New(callable, NULL);
@@ -117,21 +141,21 @@
}
static PyObject*
-test_raise_dead_greenlet(PyObject* self)
+test_raise_dead_greenlet(PyObject* UNUSED(self))
{
PyErr_SetString(PyExc_GreenletExit, "test GreenletExit exception.");
return NULL;
}
static PyObject*
-test_raise_greenlet_error(PyObject* self)
+test_raise_greenlet_error(PyObject* UNUSED(self))
{
PyErr_SetString(PyExc_GreenletError, "test greenlet.error exception");
return NULL;
}
static PyObject*
-test_throw(PyObject* self, PyGreenlet* g)
+test_throw(PyObject* UNUSED(self), PyGreenlet* g)
{
const char msg[] = "take that sucka!";
PyObject* msg_obj = Py_BuildValue("s", msg);
@@ -144,7 +168,7 @@
}
static PyObject*
-test_throw_exact(PyObject* self, PyObject* args)
+test_throw_exact(PyObject* UNUSED(self), PyObject* args)
{
PyGreenlet* g = NULL;
PyObject* typ = NULL;
@@ -227,5 +251,8 @@
}
PyGreenlet_Import();
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
+#endif
return module;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/greenlet-3.2.4/src/greenlet/tests/_test_extension_cpp.cpp
new/greenlet-3.3.0/src/greenlet/tests/_test_extension_cpp.cpp
--- old/greenlet-3.2.4/src/greenlet/tests/_test_extension_cpp.cpp
2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/tests/_test_extension_cpp.cpp
2025-12-04 15:19:40.000000000 +0100
@@ -81,7 +81,7 @@
static PyObject*
-py_test_exception_throw_nonstd(PyObject* self, PyObject* args)
+py_test_exception_throw_nonstd(PyObject* UNUSED(self), PyObject* args)
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
@@ -91,7 +91,7 @@
}
static PyObject*
-py_test_exception_throw_std(PyObject* self, PyObject* args)
+py_test_exception_throw_std(PyObject* UNUSED(self), PyObject* args)
{
if (!PyArg_ParseTuple(args, ""))
return NULL;
@@ -101,7 +101,7 @@
}
static PyObject*
-py_test_call(PyObject* self, PyObject* arg)
+py_test_call(PyObject* UNUSED(self), PyObject* arg)
{
PyObject* noargs = PyTuple_New(0);
PyObject* ret = PyObject_Call(arg, noargs, nullptr);
@@ -121,7 +121,7 @@
* segfault the process.
*/
static PyObject*
-test_exception_switch_and_do_in_g2(PyObject* self, PyObject* args)
+test_exception_switch_and_do_in_g2(PyObject* UNUSED(self), PyObject* args)
{
PyObject* g2func = NULL;
PyObject* result = NULL;
@@ -221,6 +221,9 @@
p_test_exception_throw_nonstd = test_exception_throw_nonstd;
p_test_exception_throw_std = test_exception_throw_std;
p_test_exception_switch_recurse = test_exception_switch_recurse;
+#ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
+#endif
return module;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/tests/leakcheck.py
new/greenlet-3.3.0/src/greenlet/tests/leakcheck.py
--- old/greenlet-3.2.4/src/greenlet/tests/leakcheck.py 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/tests/leakcheck.py 2025-12-04
15:19:40.000000000 +0100
@@ -172,7 +172,7 @@
# to try to reverse the order of arguments...which leads
# to the explosion of mock objects. We don't want that, so we implement
# the check manually.
- if kind == type(self._include_object_p):
+ if kind == type(self._include_object_p): # pylint:
disable=unidiomatic-typecheck
try:
# pylint:disable=not-callable
exact_method_equals = self._include_object_p.__eq__(obj)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/tests/test_gc.py
new/greenlet-3.3.0/src/greenlet/tests/test_gc.py
--- old/greenlet-3.2.4/src/greenlet/tests/test_gc.py 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/tests/test_gc.py 2025-12-04
15:19:40.000000000 +0100
@@ -11,7 +11,7 @@
# which is no longer optional.
assert greenlet.GREENLET_USE_GC
-class GCTests(TestCase):
+class TestGC(TestCase):
def test_dead_circular_ref(self):
o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch())
gc.collect()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet/tests/test_greenlet.py
new/greenlet-3.3.0/src/greenlet/tests/test_greenlet.py
--- old/greenlet-3.2.4/src/greenlet/tests/test_greenlet.py 2025-08-07
15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet/tests/test_greenlet.py 2025-12-04
15:19:40.000000000 +0100
@@ -1134,8 +1134,9 @@
class TestRepr(TestCase):
- def assertEndsWith(self, got, suffix):
- self.assertTrue(got.endswith(suffix), (got, suffix))
+ if not hasattr(TestCase, 'assertEndsWith'): # Added in 3.14
+ def assertEndsWith(self, s, suffix, msg=None):
+ self.assertTrue(s.endswith(suffix), (s, suffix, msg))
def test_main_while_running(self):
r = repr(greenlet.getcurrent())
@@ -1349,5 +1350,16 @@
output
)
+class TestModule(TestCase):
+
+ @unittest.skipUnless(hasattr(sys, '_is_gil_enabled'),
+ "Needs 3.13 and above for sys._is_gil_enabled")
+ def test_no_gil_on_free_threaded(self):
+
+ if RUNNING_ON_FREETHREAD_BUILD:
+ self.assertFalse(sys._is_gil_enabled())
+ else:
+ self.assertTrue(sys._is_gil_enabled())
+
if __name__ == '__main__':
unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/src/greenlet.egg-info/PKG-INFO
new/greenlet-3.3.0/src/greenlet.egg-info/PKG-INFO
--- old/greenlet-3.2.4/src/greenlet.egg-info/PKG-INFO 2025-08-07
15:13:39.000000000 +0200
+++ new/greenlet-3.3.0/src/greenlet.egg-info/PKG-INFO 2025-12-04
15:19:48.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: greenlet
-Version: 3.2.4
+Version: 3.3.0
Summary: Lightweight in-process concurrent programming
Home-page: https://greenlet.readthedocs.io/
Author: Alexey Borzenkov
@@ -21,14 +21,14 @@
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.9
+Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
License-File: LICENSE.PSF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/greenlet-3.2.4/tox.ini new/greenlet-3.3.0/tox.ini
--- old/greenlet-3.2.4/tox.ini 2025-08-07 15:13:36.000000000 +0200
+++ new/greenlet-3.3.0/tox.ini 2025-12-04 15:19:40.000000000 +0100
@@ -1,6 +1,6 @@
[tox]
envlist =
- py{37,38,39,310,311,312,313},py{310,311,312,313}-ns,docs
+
py{37,38,39,310,311,312,313,314},py{310,311,312,313,314}-ns,docs,py314t,tsan-314#,tsan-314t
[testenv]
commands =
@@ -21,3 +21,13 @@
sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest
extras = docs
+
+[testenv:tsan-314t]
+basepython = /usr/local/python-builds/tsan/bin/python3.14t
+passenv =
+ TSAN_OPTIONS
+
+[testenv:tsan-314]
+basepython = /usr/local/python-builds/default-tsan/bin/python3.14
+passenv =
+ TSAN_OPTIONS