WSGIFreeThreading
- Description:
Enable free-threaded Python (GIL disabled) for the matched process.
- Syntax:
WSGIFreeThreading On|Off- Default:
WSGIFreeThreading Off- Context:
server config
Controls whether the Python interpreter in the matched process is started with the GIL disabled, as introduced by PEP 703 in Python 3.13. Free-threading is a process-wide setting fixed at interpreter initialisation time; it cannot be scoped to a single sub interpreter.
mod_wsgi forces the GIL on by default even on a free-threaded Python build. Free-threading is opt-in per process via this directive.
For background on the three GIL modes mod_wsgi supports and worked examples mixing them across processes, see GIL Modes and Free-Threading.
To enable free-threading for every process where mod_wsgi initialises Python (the embedded interpreter and every daemon process group), set the directive at server config scope:
WSGIFreeThreading On
To enable it only for selected processes, place it inside a
WSGIInterpreterOptions container with a process-group=
selector. A nested setting overrides the top-level default for the
matching process only.
Selectors
Permitted scoping for WSGIFreeThreading:
Top-level (no container): matches every process.
<WSGIInterpreterOptions>with no selectors: matches every process. Same effect as a top-level setting.<WSGIInterpreterOptions process-group=%{GLOBAL}>: matches the embedded interpreter only. Daemon processes are unaffected.<WSGIInterpreterOptions process-group=NAME>: matches the named daemon process group only.
The directive is not valid in a container that has
application-group= set. Free-threading is a process-wide setting
and cannot be scoped per application group. If the directive appears
in such a container a warning is logged at config load and the
directive is ignored for that container; see WSGI0201 — WSGIFreeThreading inside container with application-group= is ignored.
Python version requirements
Free-threading requires Python 3.13 or later, configured at build
time with --disable-gil. mod_wsgi must be built against that
Python interpreter (the C macro Py_GIL_DISABLED must be defined).
If WSGIFreeThreading On is set on a Python build that does not
support free-threading, a warning is logged at config load and the
directive has no effect; see WSGI0200 — WSGIFreeThreading On has no effect on this Python build.
Default behaviour on free-threaded builds
A free-threaded Python build will, by default, leave the choice of
whether to enable the GIL to extension declarations: the GIL stays
off if every loaded extension declares
Py_mod_gil = Py_MOD_GIL_NOT_USED, otherwise CPython re-enables
the GIL at extension import time and emits a runtime warning.
mod_wsgi overrides this by setting PyConfig.enable_gil explicitly
on free-threaded builds, defaulting to _PyConfig_GIL_ENABLE
(GIL on) for every process. This trades CPython’s automatic
“decide based on extensions” behaviour for predictable startup. To
opt a process into free-threading, set WSGIFreeThreading On for
that process; mod_wsgi then sets enable_gil to
_PyConfig_GIL_DISABLE.
C extension compatibility
A Python C extension imported by an interpreter running with the GIL
disabled should declare its support for the no-GIL build with a
Py_mod_gil = Py_MOD_GIL_NOT_USED slot in its multi-phase init
table. Extensions that do not declare the slot are imported anyway
under the explicit _PyConfig_GIL_DISABLE mod_wsgi sets, but
CPython logs a runtime warning per extension to flag that they have
not been audited for the no-GIL runtime.
This is the most common first-time issue when enabling
WSGIFreeThreading: extensions that work fine under the GIL log
warnings (or behave incorrectly) under the no-GIL runtime. Pure
Python modules and extensions that have been audited for free-
threading are unaffected.
Interaction with WSGIPerInterpreterGIL
WSGIPerInterpreterGIL controls per-sub-interpreter GIL (PEP
684) within a process that has a GIL. It is meaningful only when
WSGIFreeThreading is not active for the same process. When
both apply to the same process, free-threading wins:
WSGIPerInterpreterGIL is a no-op and a warning is logged at sub
interpreter creation time; see WSGI0202 — Per-interpreter GIL skipped because free-threading is active.
The two directives can therefore be mixed across different processes in the same Apache server. For example, on a free-threaded Python build:
The embedded interpreter can run free-threaded while one daemon process group runs with the GIL enabled and selected sub interpreters in that daemon process get their own GIL.
Or one daemon process group can run free-threaded while another runs with the GIL enabled and selected sub interpreters get their own GIL.
Interaction with WSGISwitchInterval
The WSGISwitchInterval directive (and the switch-interval=
parameter on WSGIDaemonProcess) controls how often the GIL is
yielded. With no GIL there is nothing to yield, so both are no-ops
when WSGIFreeThreading is active for the process. mod_wsgi
warns and skips the sys.setswitchinterval() call; see
WSGI0203 — WSGISwitchInterval skipped in embedded process because free-threading is active, WSGI0204 — Daemon-group switch-interval skipped because free-threading is active and WSGI0205 — Per-interpreter switch interval skipped because free-threading is active.