diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 37cbbf93..44f56878 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,13 +25,11 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest] include: # Needs to be all supported Python versions, they upload the built # wheels for releases. - - os: macos-latest - python-version: 3.8 - os: macos-latest python-version: 3.9 - os: macos-latest diff --git a/CHANGES.rst b/CHANGES.rst index bce2f1af..b5d3a6b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,10 @@ Changes ========= -3.1.2 (unreleased) +3.2.0 (unreleased) ================== -- Nothing changed yet. +- Remove support for Python 3.7 and 3.8. 3.1.1 (2024-09-20) diff --git a/appveyor.yml b/appveyor.yml index 4fb72ea7..8d595b92 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -69,17 +69,6 @@ environment: PYTHON_EXE: python - - PYTHON: "C:\\Python38-x64" - PYTHON_ARCH: "64" - PYTHON_VERSION: "3.8.x" - PYTHON_EXE: python - - - - PYTHON: "C:\\Python37-x64" - PYTHON_ARCH: "64" - PYTHON_VERSION: "3.7.x" - PYTHON_EXE: python - # Tested 32-bit versions. A small, hand-picked selection covering # important variations. No need to include newer versions of @@ -100,17 +89,7 @@ environment: # Untested 32-bit versions. As above, we don't expect any variance # from the tested 32-bit versions, OR they are very EOL. - - PYTHON: "C:\\Python38" - PYTHON_ARCH: "32" - PYTHON_VERSION: "3.8.x" - PYTHON_EXE: python - GWHEEL_ONLY: true - - - PYTHON: "C:\\Python37" - PYTHON_ARCH: "32" - PYTHON_VERSION: "3.7.x" - PYTHON_EXE: python - GWHEEL_ONLY: true + # None right now. diff --git a/make-manylinux b/make-manylinux index 677e4898..795cf439 100755 --- a/make-manylinux +++ b/make-manylinux @@ -35,7 +35,7 @@ if [ -d /greenlet -a -d /opt/python ]; then which auditwheel echo "Installed Python versions" ls -l /opt/python - for variant in `ls -d /opt/python/cp{313,37,38,39,310,311,312}*`; do + for variant in `ls -d /opt/python/cp{313,39,310,311,312}*`; do if [ "$variant" = "/opt/python/cp313-313t" ]; then echo "Skipping no-gil build. The GIL is required." continue diff --git a/setup.py b/setup.py index 088d1e5b..90ee73c5 100755 --- a/setup.py +++ b/setup.py @@ -241,8 +241,6 @@ def get_greenlet_version(): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -261,6 +259,6 @@ def get_greenlet_version(): 'psutil', ], }, - python_requires=">=3.7", + python_requires=">=3.9", zip_safe=False, ) diff --git a/src/greenlet/TThreadStateDestroy.cpp b/src/greenlet/TThreadStateDestroy.cpp index 37fcc8c1..449b7887 100644 --- a/src/greenlet/TThreadStateDestroy.cpp +++ b/src/greenlet/TThreadStateDestroy.cpp @@ -15,7 +15,6 @@ #include "TGreenlet.hpp" #include "greenlet_thread_support.hpp" -#include "greenlet_cpython_add_pending.hpp" #include "greenlet_compiler_compat.hpp" #include "TGreenletGlobals.cpp" #include "TThreadState.hpp" @@ -168,47 +167,7 @@ struct ThreadState_DestroyNoGIL delete state; // Deleting this runs the destructor, DECREFs the main greenlet. } - // ensure this is actually defined. - static_assert(GREENLET_BROKEN_PY_ADD_PENDING == 1 || GREENLET_BROKEN_PY_ADD_PENDING == 0, - "GREENLET_BROKEN_PY_ADD_PENDING not defined correctly."); -#if GREENLET_BROKEN_PY_ADD_PENDING - static int _push_pending_call(struct _pending_calls *pending, - int (*func)(void *), void *arg) - { - int i = pending->last; - int j = (i + 1) % NPENDINGCALLS; - if (j == pending->first) { - return -1; /* Queue full */ - } - pending->calls[i].func = func; - pending->calls[i].arg = arg; - pending->last = j; - return 0; - } - - static int AddPendingCall(int (*func)(void *), void *arg) - { - _PyRuntimeState *runtime = &_PyRuntime; - if (!runtime) { - // obviously impossible - return 0; - } - struct _pending_calls *pending = &runtime->ceval.pending; - if (!pending->lock) { - return 0; - } - int result = 0; - PyThread_acquire_lock(pending->lock, WAIT_LOCK); - if (!pending->finishing) { - result = _push_pending_call(pending, func, arg); - } - PyThread_release_lock(pending->lock); - SIGNAL_PENDING_CALLS(&runtime->ceval); - return result; - } -#else - // 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 @@ -234,7 +193,7 @@ struct ThreadState_DestroyNoGIL } return Py_AddPendingCall(func, arg); } -#endif + diff --git a/src/greenlet/greenlet_cpython_add_pending.hpp b/src/greenlet/greenlet_cpython_add_pending.hpp deleted file mode 100644 index 0d28efd3..00000000 --- a/src/greenlet/greenlet_cpython_add_pending.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef GREENLET_CPYTHON_ADD_PENDING_HPP -#define GREENLET_CPYTHON_ADD_PENDING_HPP - -#if (PY_VERSION_HEX >= 0x30800A0 && PY_VERSION_HEX < 0x3090000) && !(defined(_WIN32) || defined(WIN32)) -// XXX: From Python 3.8a3 [1] up until Python 3.9a6 [2][3], -// ``Py_AddPendingCall`` would try to produce a Python exception if -// the interpreter was in the beginning of shutting down when this -// function is called. However, ``Py_AddPendingCall`` doesn't require -// the GIL, and we are absolutely not holding it when we make that -// call. That means that trying to create the Python exception is -// using the C API in an undefined state; here the C API detects this -// and aborts the process with an error ("Fatal Python error: Python -// memory allocator called without holding the GIL": Add -> -// PyErr_SetString -> PyUnicode_New -> PyObject_Malloc). This arises -// (obviously) in multi-threaded programs and happens if one thread is -// exiting and cleaning up its thread-local data while the other -// thread is trying to shut down the interpreter. A crash on shutdown -// is still a crash and could result in data loss (e.g., daemon -// threads are still running, pending signal handlers may be present, -// buffers may not be flushed, there may be __del__ that need run, -// etc), so we have to work around it. -// -// Of course, we can (and do) check for whether the interpreter is -// shutting down before calling ``Py_AddPendingCall``, but that's a -// race condition since we don't hold the GIL, and so we may not -// actually get the right answer. Plus, ``Py_FinalizeEx`` actually -// calls ``_Py_FinishPendingCalls`` (which sets the pending->finishing -// flag, which is used to gate creating the exceptioen) *before* -// publishing any other data that would let us detect the shutdown -// (such as runtime->finalizing). So that point is moot. -// -// Our solution for those versions is to inline the same code, without -// the problematic bit that sets the exception. Unfortunately, all of -// the structure definitions are private/opaque, *and* we can't -// actually count on being able to include their definitions from -// ``internal/pycore_*``, because on some platforms those header files -// are incomplete (i.e., on macOS with macports 3.8, the includes are -// fine, but on Ubuntu jammy with 3.8 from ppa:deadsnakes or GitHub -// Actions 3.8 (I think it's Ubuntu 18.04), they con't be used; at -// least, I couldn't get them to work). So we need to define the -// structures and _PyRuntime data member ourself. Yet more -// unfortunately, _PyRuntime won't link on Windows, so we can only do -// this on other platforms. -// -// [1] https://github.com/python/cpython/commit/842a2f07f2f08a935ef470bfdaeef40f87490cfc -// [2] https://github.com/python/cpython/commit/cfc3c2f8b34d3864717ab584c5b6c260014ba55a -// [3] https://github.com/python/cpython/issues/81308 -# define GREENLET_BROKEN_PY_ADD_PENDING 1 - -// When defining these structures, the important thing is to get -// binary compatibility, i.e., structure layout. For that, we only -// need to define fields up to the ones we use; after that they're -// irrelevant UNLESS the structure is included in another structure -// *before* the structure we're interested in --- in that case, it -// must be complete. Ellipsis indicate elided trailing members. -// Pointer types are changed to void* to keep from having to define -// more structures. - -// From "internal/pycore_atomic.h" - -// There are several different definitions of this, including the -// plain ``int`` version, a ``volatile int`` and an ``_Atomic int`` -// I don't think any of those change the size/layout. -typedef struct _Py_atomic_int { - volatile int _value; -} _Py_atomic_int; - -// This needs too much infrastructure, so we just do a regular store. -#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \ - (ATOMIC_VAL)->_value = NEW_VAL - - - -// From "internal/pycore_pymem.h" -#define NUM_GENERATIONS 3 - - -struct gc_generation { - PyGC_Head head; // We already have this defined. - int threshold; - int count; -}; -struct gc_generation_stats { - Py_ssize_t collections; - Py_ssize_t collected; - Py_ssize_t uncollectable; -}; - -struct _gc_runtime_state { - void *trash_delete_later; - int trash_delete_nesting; - int enabled; - int debug; - struct gc_generation generations[NUM_GENERATIONS]; - void *generation0; - struct gc_generation permanent_generation; - struct gc_generation_stats generation_stats[NUM_GENERATIONS]; - int collecting; - void *garbage; - void *callbacks; - Py_ssize_t long_lived_total; - Py_ssize_t long_lived_pending; -}; - -// From "internal/pycore_pystate.h" -struct _pending_calls { - int finishing; - PyThread_type_lock lock; - _Py_atomic_int calls_to_do; - int async_exc; -#define NPENDINGCALLS 32 - struct { - int (*func)(void *); - void *arg; - } calls[NPENDINGCALLS]; - int first; - int last; -}; - -struct _ceval_runtime_state { - int recursion_limit; - int tracing_possible; - _Py_atomic_int eval_breaker; - _Py_atomic_int gil_drop_request; - struct _pending_calls pending; - // ... -}; - -typedef struct pyruntimestate { - int preinitializing; - int preinitialized; - int core_initialized; - int initialized; - void *finalizing; - - struct pyinterpreters { - PyThread_type_lock mutex; - void *head; - void *main; - int64_t next_id; - } interpreters; - // XXX Remove this field once we have a tp_* slot. - struct _xidregistry { - PyThread_type_lock mutex; - void *head; - } xidregistry; - - unsigned long main_thread; - -#define NEXITFUNCS 32 - void (*exitfuncs[NEXITFUNCS])(void); - int nexitfuncs; - - struct _gc_runtime_state gc; - struct _ceval_runtime_state ceval; - // ... -} _PyRuntimeState; - -#define SIGNAL_PENDING_CALLS(ceval) \ - do { \ - _Py_atomic_store_relaxed(&(ceval)->pending.calls_to_do, 1); \ - _Py_atomic_store_relaxed(&(ceval)->eval_breaker, 1); \ - } while (0) - -extern _PyRuntimeState _PyRuntime; - -#else -# define GREENLET_BROKEN_PY_ADD_PENDING 0 -#endif - - -#endif