Skip to content

Conversation

@nbouvrette
Copy link

@nbouvrette nbouvrette commented Feb 11, 2026

Summary

This PR restores Python 3.9 support that was dropped in greenlet 3.3.0.

Context

When Python 3.9 was dropped in 3.3.0, no C/C++ code was actually removed — the change was purely in packaging metadata (requires-python and trove classifiers in pyproject.toml) and CI configuration. The tox.ini test matrix already includes py39. This means restoring 3.9 is a minimal, low-risk change.

Why Restore 3.9?

Python 3.9 remains widely deployed in production environments. Many teams (including ours) run mission-critical services on Python 3.9 with greenlet-dependent stacks (uWSGI, gevent, SQLAlchemy) and cannot immediately upgrade Python.

With the safe finalization fix in PR #495, the primary stability concern for Python 3.9 (SIGSEGV/SIGABRT during interpreter shutdown) is now resolved. Restoring 3.9 support gives these teams a stable greenlet release path while they plan their Python version upgrade.

Changes

  1. pyproject.toml:

    • requires-python: ">=3.10"">=3.9"
    • Added "Programming Language :: Python :: 3.9" trove classifier
  2. .github/workflows/tests.yml:

    • Added "3.9" to the CI test matrix
    • Added windows-11-arm exclusion for 3.9 (not available on that platform)
  3. CHANGES.rst: Added entries for both this change and the finalization fix from PR Fix SIGSEGV/SIGABRT during interpreter shutdown on Python < 3.11 #495.

Dependencies

This PR builds on top of PR #495 (safe finalization fix). Without that fix, Python 3.9 would still crash during interpreter shutdown when active greenlets exist. With both PRs merged, Python 3.9 is fully stable.

Test Plan

  • CI runs tests on Python 3.9 (ubuntu, macOS, windows) — now included in the matrix
  • All existing tests pass on Python 3.9
  • tox -e py39 passes (already in the tox config)
  • CI builds wheels for Python 3.9

@nbouvrette nbouvrette force-pushed the fix/restore-python39-support branch from 094f77a to 317415d Compare February 11, 2026 04:59
nbouvrette and others added 2 commits February 10, 2026 23:50
During interpreter finalization (Py_FinalizeEx), active greenlets being
deallocated would trigger g_switch() to throw GreenletExit. This performs
a stack switch and executes Python code in a partially-torn-down
interpreter, causing:
  - SIGSEGV (signal 11) on greenlet 3.x
  - SIGABRT (signal 6 / "Accessing state after destruction") on greenlet 2.x

On Python >= 3.11, CPython's restructured finalization internals (frame
representation, data stack management, recursion tracking) make g_switch()
during finalization safe. On Python < 3.11, this was not the case.

This commit adds two guards, compiled only on Python < 3.11
(!GREENLET_PY311):

1. In _green_dealloc_kill_started_non_main_greenlet (PyGreenlet.cpp):
   When the interpreter is finalizing, call murder_in_place() directly
   instead of attempting g_switch(). This marks the greenlet as dead
   without throwing GreenletExit, avoiding the crash at the cost of not
   running cleanup code inside the greenlet.

2. In ~ThreadState (TThreadState.hpp):
   When the interpreter is finalizing, skip the GC-based leak detection
   that calls PyImport_ImportModule("gc"), which is unsafe when the
   import machinery is partially torn down. Only perform minimal safe
   cleanup (clearing strong references).

On Python >= 3.11, no changes are made — the existing behavior (throwing
GreenletExit via g_switch, running cleanup code) continues to work
correctly during finalization.

Also adds test_interpreter_shutdown.py with 9 subprocess-based tests
covering:
  - Single/multiple/nested/threaded/deeply-nested active greenlets at
    shutdown (no-crash safety on all Python versions)
  - Version-aware behavioral tests verifying that GreenletExit cleanup
    code runs on Python >= 3.11 but is correctly skipped on < 3.11
  - Edge cases: active exception context, stress test with 50 greenlets

Fixes python-greenlet#411
See also python-greenlet#351, python-greenlet#376

Co-authored-by: Cursor <[email protected]>
Re-add Python 3.9 to requires-python, trove classifiers, and CI test
matrix. No C/C++ code was removed when 3.9 support was dropped in
3.3.0 — the drop was purely a packaging metadata change.

Combined with the safe finalization fix in the parent commit (PR python-greenlet#495),
greenlet now works reliably on Python 3.9 during interpreter shutdown,
which was the primary stability concern for older Python versions.

Changes:
- pyproject.toml: requires-python >= 3.9, add 3.9 trove classifier
- .github/workflows/tests.yml: add "3.9" to test matrix, exclude
  windows-11-arm (not available for 3.9)
- CHANGES.rst: add entries for both this change and the finalization fix

Co-authored-by: Cursor <[email protected]>
@nbouvrette nbouvrette force-pushed the fix/restore-python39-support branch from 317415d to fc0ec82 Compare February 11, 2026 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant