WSGIInterpreterOptions

Description:

Container for per-interpreter configuration directives.

Syntax:

<WSGIInterpreterOptions [process-group=NAME] [application-group=NAME]> ... </WSGIInterpreterOptions>

Context:

server config

Section directive that scopes a set of per-interpreter configuration directives to interpreters matching the supplied selector keys. The container exists so that properties of an individual Python sub interpreter (or set of sub interpreters) can be configured independently of the process-wide defaults.

Example:

<WSGIInterpreterOptions process-group=daemon-1 application-group=app1>
    WSGIPerInterpreterGIL On
    WSGISwitchInterval 0.002
    WSGIPythonPath /opt/app1/lib
</WSGIInterpreterOptions>

For the GIL-mode side of this container’s behaviour, including worked examples mixing modes across processes, see GIL Modes and Free-Threading.

Selectors

Both process-group= and application-group= are optional. Omitting a key means the container matches any value for that dimension. The empty container <WSGIInterpreterOptions> matches every interpreter.

The process-group= value matches against the name of a WSGIDaemonProcess group, or against the empty string for embedded mode interpreters created in Apache child processes.

The application-group= value matches against the resolved application group name at interpreter-creation time. So application-group=app1 matches when a WSGIApplicationGroup app1 directive (or equivalent application-group= parameter on WSGIScriptAlias, WSGIImportScript or WSGIHandlerScript) selects app1.

The literal value %{GLOBAL} is accepted as a synonym for the empty string on both selectors. On application-group= it matches the main Python interpreter. On process-group= it matches embedded mode (Apache child processes that have not been routed to a daemon group).

Variable-expanded application group names of the form %{ENV:VAR} are resolved at request time, after the configuration has been loaded, and the container match is evaluated then.

Permitted child directives

Only the following directives are valid inside the container:

Any other directive placed inside the container is a configuration error.

When a directive listed above appears at server config scope without being inside a container, it continues to behave as documented on its own page. Containers are purely additive; existing configurations continue to work unchanged.

Inheritance and merge rules

When an interpreter is created, mod_wsgi walks every container defined at server config scope and selects those whose selectors match the interpreter’s resolved process group and application group names.

Each matching container has a specificity score equal to the count of non-wildcard selector keys: 0 for <WSGIInterpreterOptions>, 1 for a single-key container, 2 for one with both keys set. Top-level directives outside any container are treated as the lowest layer.

For the scalar directives (WSGIPerInterpreterGIL, WSGISwitchInterval, WSGIRestrictStdin, WSGIRestrictStdout, WSGIRestrictSignal) the most-specific matching layer wins. If two matching containers share the same specificity, the one written later in the configuration wins. If no container matches, the top-level value applies.

For WSGIPythonPath every matching layer contributes. See WSGIPythonPath layering below.

WSGIFreeThreading is resolved separately from the per-interpreter directives because free-threading is a process-wide setting fixed at Py_InitializeFromConfig time. The resolver walks containers matching the current process’s process-group= selector only; containers with application-group= set are skipped (and warned at config load). The resolved value drives PyConfig.enable_gil and is then read back by sub interpreter creation and switch interval sites to suppress configuration that has no effect when the GIL is disabled. See WSGIFreeThreading for the full rules.

WSGIPythonPath layering

WSGIPythonPath is additive across matching layers rather than last-write-wins. Each layer’s directory list is processed in turn, least-specific first, most-specific last. For each directory in a layer mod_wsgi calls site.addsitedir() so that any .pth files in that directory are processed normally, then hoists the entries that site.addsitedir() added to the front of sys.path.

Because layers are applied least-specific to most-specific and each hoist places entries at the front, the most-specific layer’s directories end up at the very front of sys.path once all layers have been applied.

The base layer for WSGIPythonPath depends on which interpreter is being created:

  • For embedded mode interpreters the base layer is the top-level WSGIPythonPath directive, if set.

  • For daemon mode interpreters the base layer is the python-path= parameter on the matching WSGIDaemonProcess directive, if set.

The container’s WSGIPythonPath is purely additive on top of the applicable base layer. It never replaces the base layer. To use a different base in daemon mode, set python-path= on the WSGIDaemonProcess directive.

The empty container:

<WSGIInterpreterOptions>
WSGIPythonPath /opt/lib
</WSGIInterpreterOptions>

adds /opt/lib to both embedded and daemon interpreters, which is something there is no way to express with a single top-level directive.

Switch interval validation

A WSGIPerInterpreterGIL setting only changes the GIL configuration of sub interpreters. Under the shared GIL the Python interpreter’s switch interval is a process-global value: every interpreter in the process sees the same switch interval, regardless of which one sys.setswitchinterval() is called from.

Setting WSGISwitchInterval inside a container that selects on application-group= therefore requires that the matching interpreter also resolves to per-interpreter GIL after the merge. Otherwise the directive would appear to be application-group-scoped while silently mutating the process-global value.

For statically-named application-group= containers the check runs at configuration load. For application-group=%{ENV:VAR} containers the check runs at sub-interpreter creation time; if the check fails the switch interval setting is skipped for that interpreter and a warning is logged. The request itself still proceeds.

Containers selecting only on process-group= (or with no selectors at all) are always safe with WSGISwitchInterval, because every interpreter in the matched scope shares whatever GIL configuration is in force.

Examples

Process-wide opt-in to per-interpreter GIL:

WSGIPerInterpreterGIL On

Per-interpreter GIL only for a single application group, leaving the rest of the process on the shared GIL:

<WSGIInterpreterOptions application-group=app1>
    WSGIPerInterpreterGIL On
</WSGIInterpreterOptions>

Per-interpreter GIL for every interpreter in a named daemon process group:

<WSGIInterpreterOptions process-group=daemon-1>
    WSGIPerInterpreterGIL On
</WSGIInterpreterOptions>

Tuning the GIL switch interval for one application that has its own GIL, while leaving the rest of the process untouched:

<WSGIInterpreterOptions application-group=app1>
    WSGIPerInterpreterGIL On
    WSGISwitchInterval 0.001
</WSGIInterpreterOptions>

Adding a directory to sys.path for one application without disturbing the daemon-wide path:

WSGIDaemonProcess daemon-1 python-path=/opt/daemon-1
<WSGIInterpreterOptions process-group=daemon-1 application-group=app1>
    WSGIPythonPath /opt/app1/src:/opt/app1/lib
</WSGIInterpreterOptions>

Restricting sys.stdout for a single application group while leaving the process default unchanged:

<WSGIInterpreterOptions application-group=app1>
    WSGIRestrictStdout On
</WSGIInterpreterOptions>