Skip to content

Drop DISABLE_SQLALCHEMY_CEXT_RUNTIME=1 once SQLAlchemy's cyextensions declare free-thread safety #160

Description

@lesnik512

What

The freethreaded CI job (.github/workflows/_checks.yml) sets DISABLE_SQLALCHEMY_CEXT_RUNTIME=1,
and docs/introduction/installation.md tells users to set the same variable for a genuinely
GIL-free process. Both are a workaround for an upstream gap, and both should come out once upstream
closes it.

Why it is there

SQLAlchemy ships cp314t wheels, but its Cython extensions do not declare Py_MOD_GIL_NOT_USED, so
CPython force-re-enables the GIL process-wide on import sqlalchemy. A free-threaded wheel is
necessary but not sufficient for a GIL-free run. asyncpg and pydantic-core are unaffected —
SQLAlchemy is the only offender in the dependency graph.

Reproduced on CPython 3.14.7t against SQLAlchemy 2.0.52 (current latest):

$ python -c "import sys, sqlalchemy; print(sys._is_gil_enabled())"
<frozen importlib._bootstrap>:491: RuntimeWarning: The global interpreter lock (GIL) has been
enabled to load module 'sqlalchemy.cyextension.collections', which has not declared that it can
run safely without the GIL.
True

$ DISABLE_SQLALCHEMY_CEXT_RUNTIME=1 python -c "import sys, sqlalchemy; print(sys._is_gil_enabled())"
False

CPython emits the diagnostic itself, naming sqlalchemy.cyextension.collections. The variable is
SQLAlchemy's own documented switch to the pure-Python implementations of the same behaviour — not a
hack — but the fallback is slower, so it is a real (if small) cost we are paying only to keep the
GIL-off assertion meaningful.

Why it matters at all

The library runs correctly on 3.14t either way; the GIL state does not change outbox semantics. It
matters for a user running other threaded code in the same process, whose parallelism SQLAlchemy's
cyextensions would otherwise kill process-wide. That is the guarantee docs/introduction/installation.md
advertises, and the CI assertion is what keeps it honest.

Done when

  • SQLAlchemy's Cython extensions declare Py_MOD_GIL_NOT_USED.
  • Re-running the freethreaded job's assertion without the variable still reports
    sys._is_gil_enabled() is False.
  • Then: remove the env: entry from the job, the caveat block in
    docs/introduction/installation.md, and the corresponding paragraph in
    docs/adr/0002-free-threading-is-compat-only.md.

Upstream's free-threading work is sqlalchemy/sqlalchemy#12881, which covered building and testing
cp314t wheels and is closed; declaring the extensions themselves GIL-safe was not part of it, so
there is no upstream tracking issue to watch yet. Worth re-checking on each SQLAlchemy minor.

Note

The docs caveat also mentions that the same problem applies to foreign-broker clients used with the
relay feature (for example aiokafka). That half is not ours to fix and stays regardless.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-infoWaiting on reporter for more information

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions