Version 6.0.0
For this release a signficant review of the code base was undertaken to clean up legacy code and remove support for older versions of Python and Apache httpd. In the process a large number of fixes were made to the code base to fix up inconsistencies in how the Python C API and Apache API were used, such as error handling and reference counting. This should make the code base more robust and easier to maintain going forward. Because of the large number of changes, rather than listing all of the individual fixes, the release notes will just list the major fixes which may have had a visibe effect on users in production deployments.
New Features
Added support for opting Python sub interpreters into PEP 684’s per-interpreter GIL. The new
WSGIPerInterpreterGILdirective enables it process-wide as a default. The new<WSGIInterpreterOptions>section directive scopes the setting (and a small set of related per-interpreter directives) to individual sub interpreters viaprocess-group=andapplication-group=selectors, so per-interpreter GIL can be enabled only for selected applications while the rest of the process continues on the shared GIL.WSGISwitchInterval,WSGIPythonPath,WSGIRestrictStdin,WSGIRestrictStdoutandWSGIRestrictSignalare also valid inside the container and resolve per interpreter;WSGIPythonPathlayers additively on top of the embedded mode or daemon mode base path. Per-interpreter GIL requires Python 3.12 or later; on older Python versions the directive is accepted but logs a configuration-time warning and has no effect. C extensions imported by an opted-in sub interpreter must declarePy_mod_multiple_interpreterswithPy_MOD_PER_INTERPRETER_GIL_SUPPORTED(or be pure Python), otherwise import will fail. The main Python interpreter always uses the original process-wide GIL and a setting that resolves to it is silently ignored.Added support for being built against and run on free-threaded Python (PEP 703, Python 3.13 or later configured with
--disable-gil). The embeddedmod_wsgiPython module declaresPy_mod_gilasPy_MOD_GIL_NOT_USEDwhen compiled against a free-threaded build. By default mod_wsgi forces the GIL on even on a free-threaded build by settingPyConfig.enable_gil = _PyConfig_GIL_ENABLEat process initialisation: free-threading is opt-in per process via the newWSGIFreeThreadingdirective.WSGIFreeThreading Onat top level enables free-threading for every process; placed inside an<WSGIInterpreterOptions process-group=...>container it enables free-threading only for the matched embedded or daemon process.application-group=cannot scope this directive because free-threading is process-wide. In a process where free-threading is active,WSGIPerInterpreterGILandWSGISwitchInterval(and theWSGIDaemonProcess switch-interval=parameter) are no-ops and log a warning at the relevant initialisation site. C extensions loaded under free-threading should declarePy_mod_gilin their multi-phase init table; extensions that do not declare it are imported anyway under mod_wsgi’s explicit_PyConfig_GIL_DISABLEbut CPython logs a per-extension warning to flag that they have not been audited for the no-GIL runtime.mod_wsgi-expressexposes--free-threadingwhich emits the top-levelWSGIFreeThreading Ondirective and fails at configuration time if the running Python is not a free-threaded build.request-timeoutrecovery overhauled, with a newinterrupt-timeoutoption for opt-in injection-based recovery, a new natural-log scaling rule for the per-thread fire point, and a stale-awaregraceful-timeoutdrain check.Detection: instead of averaging elapsed time across threads (the old rule, which could only fire after
threads × request-timeoutof wedged-thread time), each thread is now compared independently againstrequest-timeout × (1 + ln(threads)). Atthreads=1this collapses torequest-timeout; atthreads=10it is ~3.3x; atthreads=25it is ~4.2x. The shape grants proportionally more patience as parallel capacity grows without letting the threshold run away. Multiple wedged threads are detected on the same schedule a single wedge would be.Recovery: when
interrupt-timeoutis non-zero, mod_wsgi attempts to interrupt only the offending thread by injecting a newmod_wsgi.RequestTimeoutexception via Python’sPyThreadState_SetAsyncExc. If the injection unwinds the stuck request within theinterrupt-timeoutgrace window, the WSGI adapter returns504 Gateway Timeoutand the worker thread returns to the pool — the daemon process keeps running, and other threads were never disturbed.RequestTimeoutderives directly fromBaseExceptionso well-written code does not catch it viaexcept Exception:; user code may catch it for cleanup but should re-raise. Wheninterrupt-timeoutis0(the default) injection is skipped and recovery falls straight through tograceful-timeoutfollowed byshutdown-timeout. Either way, detection is identical —interrupt-timeoutonly changes the recovery method.Drain: the
graceful-timeout“is the process idle yet?” check now ignores any in-flight request whose elapsed time has already exceededrequest-timeout + interrupt-timeout. A wedged thread that will not unwind voluntarily no longer pins the process inside graceful-timeout for its full configured duration; sibling requests get the chance to finish cleanly and the wedged thread rides out viashutdown-timeout’s forced kill.mod_wsgi-express--interrupt-timeoutdefaults to0; operators who want injection-based recovery opt in explicitly. Daemon mode only; embedded mode is unchanged.New
WSGISwitchIntervaldirective sets the Python GIL switch interval (sys.setswitchinterval()) for the embedded interpreter at process start. The matchingswitch-interval=option onWSGIDaemonProcessdoes the same for daemon-mode interpreters, and can be set per daemon group.mod_wsgi-expressexposes--switch-intervalwhich applies the value to both modes.Added an external telemetry pipeline. The new
WSGITelemetryServicedirective turns on a reporter thread in each mod_wsgi process (daemon-mode worker and embedded-mode Apache child) that emits per-interval binary datagrams over a local UNIX socket to a separately-distributed ingester. The companionmod_wsgi-telemetrypackage on PyPi provides the ingester, a browser UI, and a curses terminal monitor.WSGITelemetryOptionstoggles optional captures (currentlyCaptureUserAgent) andWSGISlowRequestsenables per-request slow-record reporting alongside the periodic samples.mod_wsgi-expressexposes matching--telemetry-service,--telemetry-interval,--telemetry-optionsand--slow-requestsoptions, plus the all-in-one--enable-telemetryshortcut which generates a service-script daemon that runs the ingester (and its web UI on127.0.0.1:<--telemetry-ui-port>, default8888) alongside the WSGI application and wires the matchingWSGITelemetryServicedirective automatically. Service-script daemons (WSGIDaemonProcess threads=0) skip the telemetry reporter entirely, so an ingester-hosting service script does not feed back into itself. See The External Telemetry Service for the full setup, including socket-permission handling for the recommended multi-user deployment.This is a brand-new feature and is still being iterated on. The directive set, option names, wire format and ingester CLI may change in a future release. The PyPi
mod_wsgi-telemetrypackage is versioned independently of mod_wsgi; pair an ingester release with the matching mod_wsgi release until the pipeline stabilises.Added
mod_wsgi.LogHandler, alogging.Handlersubclass shipped with mod_wsgi that routes Pythonloggingrecords through Apache’s error log while preserving the Python log level. Application output via the defaultStreamHandlerpath (print(),logging.basicConfig()without a custom handler,warnings.warn()) continues to land at[wsgi:error]regardless of the Python level, becausesys.stdout/sys.stderrwrites are emitted to Apache at a fixed error classification. Records emitted viamod_wsgi.LogHandlerinstead land at the matching Apache level tag (PythonCRITICAL/ERROR/WARNING/INFO/DEBUGmap to[wsgi:crit]/[wsgi:error]/[wsgi:warn]/[wsgi:info]/[wsgi:debug]respectively), so the operator-sideLogLevel wsgi:LEVELdirective filters application output as a ceiling on top of whatever Python-side filtering the application configured. Per-thread routing matches the existingwsgi.errorsbehaviour: records emitted while a thread is handling a request useap_log_rerrorand pick up the[remote ...]/[script ...]decoration; records from module-import or background threads useap_log_errorand land without that decoration.record.pathnameandrecord.linenoare passed through to Apache so an operator with%FinErrorLogFormatsees the application’slogger.*call site rather than the emit-site inside mod_wsgi. Opt in by attaching amod_wsgi.LogHandlerinstance to the logger chain; the default routing path is unchanged for applications that do not. See Logging from Applications for the full configuration recipe and the operator-ceiling versus application-floor model.Added
mod_wsgi.subscribe_signals, an in-process signal delivery mechanism for daemon-mode WSGI applications. Subscribers receive the newprocess_signalevent when the daemon receivesSIGHUPorSIGUSR2, with payload keyssigname(canonical string, e.g."SIGHUP") andsignum(numeric value on the current platform). Intended use cases are operator-driven configuration reload onSIGHUPand ad-hoc diagnostic actions onSIGUSR2, such as dumping Python stack traces of every active thread to the Apache error log. Delivery happens on a dedicated dispatcher thread inside the daemon, separate from the daemon shutdown signal pipe, so a long-running subscriber callback does not delay shutdown response or block worker threads handling requests. Subscribers run with each sub-interpreter’s GIL held in turn, matching the per-interpreter dispatch model already used bysubscribe_eventsandsubscribe_shutdown. In embedded mode the API exists but is inert: a call logs anAPLOG_INFOwarning together with a Python stack trace identifying the registration site, discards the callback, and returns it unchanged so decorator use does not silently nullify the user’s function symbol. Service-script daemons (WSGIDaemonProcess threads=0) are also unsupported, since the dispatcher infrastructure is created in the daemon main loop that service scripts skip; service scripts can use Python’ssignal.signal()directly becauseWSGIRestrictSignalis treated as off in that mode. See Subscribing to Events for the full API reference.
Features Changed
Django community has started adopting use of pathlib module when defining paths in the Django settings file. This would cause issues for the runmodwsgi management command for Django as it expected strings for STATIC_ROOT setting. The code has been updated to always convert STATIC_ROOT to a string in runmodwsgi to cope with people using pathlib module in their Django settings file.
The
cpu_user_timeandcpu_system_timekeys in the dict returned bymod_wsgi.request_metrics()have always been CPU utilization rates (fraction of one CPU core consumed over the sample period), not absolute times, which made their names inconsistent with the identically-named keys inmod_wsgi.process_metrics()where the values are cumulative CPU seconds since process start. New keyscpu_user_utilizationandcpu_system_utilizationhave been added carrying the same values, along withcpu_utilizationfor their sum. On multi-core systems these may exceed 1.0, matching thetop(1)convention, and they parallel the existingcapacity_utilizationkey. The originalcpu_user_timeandcpu_system_timekeys are retained as aliases for backwards compatibility but are deprecated and will be removed in a future release. A newcpu_timekey has also been added tomod_wsgi.process_metrics()and to therequest_finishedevent payload, providing the pre-computed sum of the corresponding user and system CPU seconds for that scope.The dict returned by
mod_wsgi.request_metrics()now also carries five HTTP response class counters —status_1xx,status_2xx,status_3xx,status_4xxandstatus_5xx— counting the per-class responses returned by the WSGI application during the sampling window. Their sum equalsrequest_countfor the same window, sostatus_4xx + status_5xxis a ready-made error rate numerator. A request whose WSGI application raised before callingstart_response(mod_wsgi serves a 500 in that case) is folded intostatus_5xxso the error rate matches the user-visible outcome rather than only counting explicitstart_response("500 ...", ...)paths.status_1xxis included as a tripwire — PEP 3333 forbids a WSGI application from returning a 1xx response, so a non-zero count flags a protocol violation. The per-class counters do not distinguish between specific codes (404 vs 401 vs 410, etc.); for per-code detail on slow responses, theWSGISlowRequeststelemetry stream now also carries the final HTTP status on each slow-request record.When a daemon process closes its connection or encounters a read error before returning complete response headers, the request now receives a
502 Bad Gatewayresponse instead of500 Internal Server Error. The500response is retained for the distinct case of a response header line exceeding the configured buffer size, and504 Gateway Timeoutis still used for read timeouts. The corresponding error log messages have also been reworked so that each failure mode is reported with a distinct message, and the underlying APR error string is now included for generic read failures. Deployments that alert on500responses from mod_wsgi may want to adjust monitoring to include502for upstream daemon failures.Log messages emitted by mod_wsgi no longer carry the historic
mod_wsgi (pid=NNN): `` prefix that the module manually prepended to its own output. The same information already appears in Apache's standard log line decoration — the ``[wsgi:LEVEL]module tag and the[pid NNN:tid NNN]field that Apache prepends to every entry emitted via theap_log_*family — so the manual prefix only duplicated information and produced twopid=fields per line. Log-scraping pipelines that previously matched on the literalmod_wsgi (pid=substring should match on the[wsgi:module tag instead, which is also what theLogLevel wsgi:LEVELApache directive controls.Log message wording, severity assignment, and identifier coverage have been overhauled across the module. The severity of every site at
WARNINGand above was reviewed against the actual operational impact and corrected where needed; for example, several per-request failures that were logged atCRITare nowERR, and a number of configuration diagnostics that only predict a later failure were demoted fromALERTtoWARNING. Every unique log site atWARNINGand above now also carries a stableWSGI####identifier emitted as a prefix on the rendered line, analogous to Apache’s ownAHnnnnnconvention from theAPLOGNOmacro, so a message such asWSGI0061: Unable to bind socket for daemon process '...'can be referenced by code in runbooks and bug reports independent of any future wording adjustments. Each identifier is documented in the new Error Reference page describing the cause, outcome, and recommended operator action for that condition. Lower-tier message wording has also been tightened for consistency and accuracy, and per-request hot-path detail is now consistently emitted atTRACE1to separate it from process-lifecycle events (DEBUGfor troubleshooting,INFOfor the operator-default view).The
WSGIVerboseDebuggingdirective is deprecated and now has no effect. Apache’s standardLogLeveldirective provides equivalent control with finer granularity: useLogLevel wsgi:debugto enable mod_wsgi’s daemon and interpreter lifecycle messages, andLogLevel wsgi:trace1to additionally enable per-request and per-thread-binding detail. The directive itself is still parsed (a configuration using it will continue to load) and now emits anINFO-level deprecation notice on startup; it will be removed in a future release.Population of the standard CGI variables in the WSGI environment no longer goes through Apache’s
ap_add_cgi_vars()andap_add_common_vars()helpers; mod_wsgi now sets the same variables itself. The motivation isap_add_cgi_vars(): its only way to computePATH_TRANSLATEDis to issue an Apache subrequest viaap_sub_req_lookup_uri()against the request’sPATH_INFO, which reruns translation hooks and can have surprising side effects.PATH_TRANSLATEDis not used by WSGI applications and is not part of PEP 3333, but the upstream API exposes no way to skip just that one variable, so the rest of what the two functions do had to be replicated. The replacement also drops a small set of variables that were either irrelevant or actively undesirable for an in-process WSGI interpreter:PATH_TRANSLATED(as above),GATEWAY_INTERFACE(PEP 3333 does not require it and theCGI/1.1value was misleading),SERVER_SIGNATURE(an HTML blob),REMOTE_HOST(would trigger a reverse-DNS lookup whenHostnameLookupsis on),REMOTE_IDENT(would trigger an RFC 1413 ident lookup whenIdentityCheckis on), andPATHalong with the various platform library-path variables (LD_LIBRARY_PATH,DYLD_LIBRARY_PATH, etc.) that mattered only for forked CGI children. Applications that depended on any of these will need to source the value another way.The Apache
MaxKeepAliveRequestsdirective is now set explicitly in themod_wsgi-expressgenerated configuration, with a value chosen per MPM and per mode. Formpm_eventthe value is0(unlimited), since idle keep-alive connections are parked on the listener thread and do not pin a worker. Formpm_workerandmpm_preforkin daemon mode the value is500: the MPM child is just a connection multiplexer and the cap is purely TCP hygiene, so a higher value than Apache’s core default of100amortises handshakes for clients that send many requests. Formpm_workerandmpm_preforkin embedded mode the value is100to match Apache’s core default, reflecting that the MPM child is the Python worker and the cap trades off fairness between keep-alive clients against handshake overhead. Other MPMs, includingmpm_winnton Windows and any third-party MPM, are not matched by the per-MPM blocks in the generated configuration and continue to inherit Apache’s core default. Operators who want a different value can override via the existing--include-fileoption until a dedicatedmod_wsgi-expressoption is added.The
mod_wsgi-expresswrapper implementation, including the Djangorunmodwsgimanagement command, has moved from themod_wsgi.serverPython package tomod_wsgi.express. The Apachemod_wsgi.soshared library continues to be installed undermod_wsgi.serverand is unaffected. Django projects that currently list'mod_wsgi.server'inINSTALLED_APPSshould update the entry to'mod_wsgi.express'. The oldmod_wsgi.serverPython module remains importable for backward compatibility, including therunmodwsgimanagement command, but importing it now emits aFutureWarningand the module will be removed in a future release.Per-request metrics accounting consumed by the Python API (
mod_wsgi.request_metrics()andmod_wsgi.process_metrics()) now requires an explicit opt-in via the newmod_wsgi.start_recording_metrics()function. Previously the first call torequest_metrics()enabled accounting as a side effect and returned an empty “seeding” sample, whileprocess_metrics()did not enable it at all — an asymmetry that meant an application polling onlyprocess_metrics()would never see the per-tick aggregator data come on. With the explicit opt-in, both accessors gate identically: they returnNoneuntilstart_recording_metrics()is called, and return populated dicts on every call thereafter (no more empty first-call sample). The new function is idempotent and safe to call unconditionally at application import time. When external telemetry reporting is enabled (WSGITelemetryServicedirective), the accessors continue to returnNoneregardless andstart_recording_metrics()has no observable effect from the Python API. Applications that previously relied on the lazy enable should add a call tomod_wsgi.start_recording_metrics()at module import time.The request-timing keys in the WSGI
environdictionary have been reworked to align with the corresponding fields on therequest_startedandrequest_finishedevent payloads.mod_wsgi.script_starthas been renamed tomod_wsgi.application_start(the previous name was misleading; the value was always sampled per-request at adapter entry, not at script load). All four request-timing keys (mod_wsgi.request_start,mod_wsgi.queue_start,mod_wsgi.daemon_startandmod_wsgi.application_start) are now Pythonfloatvalues in seconds since the epoch, replacing the previous decimal string of microseconds.mod_wsgi.queue_startandmod_wsgi.daemon_startare also now always present, set to0.0in embedded mode. This is a breaking change for any code reading these keys out of the WSGIenviron(in particular,float(environ["mod_wsgi.request_start"]) / 1000000is no longer needed; the value is already in seconds and is afloat, not astr).The
request_threads_bucketskey in the dict returned bymod_wsgi.request_metrics()has been renamed torequest_threads_completedto remove the misleading_bucketssuffix: every other_bucketskey in the dict is a duration histogram, whereas this one is a per-slot completed-request count.request_threads_bucketsis retained as a deprecated alias carrying the same value and will be removed in a future release.
Features Removed
Dropped support for Python versions older than 3.10. Python 2 compatibility code has been removed.
Dropped support for Apache httpd versions older than 2.4. Compatibility code for Apache httpd 1.3, 2.0, and 2.2 has been removed.
Removed built-in support for configuring and initializing the New Relic Python agent. This includes the
WSGINewRelicConfigFileandWSGINewRelicEnvironmentApache directives, and the--with-newrelic,--with-newrelic-agent,--with-newrelic-platform,--newrelic-config-file, and--newrelic-environmentoptions frommod_wsgi-express.Removed the
WSGILazyInitializationdirective. Python is now always initialized lazily in child and daemon processes after they have been forked from the Apache parent process. The old behavior of initializing Python in the Apache parent process, enabled by setting this directive toOff, is no longer supported due to security risks from running as root and memory leak issues with the Python interpreter on Apache restarts.Removed code that allowed mod_wsgi to coexist with mod_python in the same Apache instance. Since mod_python has not been actively developed since the Python 2.x era, this should be obsolete and not affect any current deployments.
Bugs Fixed
Fixed a name-based VirtualHost matching collision in the Apache configuration generated by
mod_wsgi-express setup-serverwhen the--server-namevalue matched the global server name default (typicallylocalhost). The generated_default_:<port>catch-all VirtualHost did not set an explicitServerNameand silently inherited the global default, causing it to advertise the same name as the intended*:<port>named VirtualHost. Apache’s tie-break by declaration order then routed every canonical-host request to the restrictive_default_block, which meant server-scope<Location>and<Directory>directives added via--include-filewere shadowed by the catch-all’s vhost-scope<Location />. Authorization and access control rules configured this way silently did nothing (with--allow-localhost) or rejected every request with 403 (without it). The_default_VirtualHost now setsServerName _wsgi_so it cannot collide with any operator-supplied--server-namevalue; its catch-all role for unrecognised Host headers is preserved by declaration order and is unchanged. Deployments that passed a--server-namediffering from the global default were not affected by this bug.Fixed unreachable retry-limit check in the daemon mode request dispatch loop that handles
200 Rejectedresponses sent during daemon process restart. The bound check was placed where the loop condition guaranteed it could never fire, so the intended503 Service Unavailableresponse with a “Maximum number of WSGI daemon process restart connects reached” log message was never emitted. A daemon stuck in a restart loop would instead yield a bogus200 Rejectedstatus to the client, a500from a truncated header read, or a504from a read timeout, depending on the final attempt’s outcome.Fixed handling of empty list elements in the
X-Forwarded-Forheader when processing trusted proxy headers. RFC 9110 §5.6.1 requires HTTP recipients to parse and ignore empty elements in comma-separated list headers, but the parser inwsgi_process_forwarded_forwas pushing zero-length tokens into the parsed array for inputs such asa,,b,, a, b, or values with multiple adjacent commas. WhenWSGITrustedProxieswas configured, the resulting empty string would later failapr_sockaddr_info_getduring the right-to-left trust-chain walk, breaking the walk early and producing an empty or incorrectREMOTE_ADDR. WhenWSGITrustedProxieswas not configured and the value began with a comma,REMOTE_ADDRwas set to an empty string. Empty list elements are now skipped in both code paths, soREMOTE_ADDRis derived from the first non-empty element as the header semantics intend.Fixed the
X-Forwarded-ServerandX-Forwarded-Portheaders to be stripped from the WSGI request environment when a request is received from a peer that is not in theWSGITrustedProxiesallowlist, matching the documented contract forWSGITrustedProxyHeadersand the behaviour already implemented for the other trusted-proxy header categories. Previously these two headers were only classified for rewriting theSERVER_NAMEandSERVER_PORTCGI variables when the peer was trusted, but the rawHTTP_X_FORWARDED_SERVERandHTTP_X_FORWARDED_PORTentries were left in the environment even when the peer was untrusted. While WSGI applications are expected to consultSERVER_NAMEandSERVER_PORTrather than the raw headers, any middleware that independently processes proxy headers could be misled by spoofed values, so the stripping is applied consistently for all categories listed inWSGITrustedProxyHeaders.Fixed precedence between a trusted proxy scheme header and
mod_sslwhen Apache terminates TLS directly. Thessl_is_httpscheck that setsHTTPS=1(and thereforewsgi.url_scheme=httpsin the WSGI environ) runs afterwsgi_process_proxy_headersand previously overwrote whatever a trustedX-Forwarded-Proto/X-Forwarded-SSL/X-Forwarded-Schemeheader had decided. In an unusual but valid deployment where a front proxy receives the original client over plain HTTP and speaks TLS to Apache itself, the proxy correctly reportsX-Forwarded-Proto: httpfor the client scheme, but Apache’s view of its own TLS inbound connection would override that back tohttps. The scheme check now only consultsssl_is_httpswhen no trusted scheme header was applied for the request, so an operator who has opted the proxy’s scheme header intoWSGITrustedProxyHeadersgets the proxy’s declaration honoured. Deployments where Apache terminates TLS directly without a front proxy and do not list a scheme header inWSGITrustedProxyHeadersare unaffected.Fixed an inverted case-sensitivity check in
wsgi_module_name, which computes the Python module name used to cache a WSGI script undersys.modulesby MD5-hashing the script’s absolute filename. The helper lowercases the filename before hashing so that two paths differing only in case collapse to the same cache slot on case- insensitive filesystems. The guard around this lowercasing evaluated theWSGICaseSensitivityflag the wrong way round: it lowercased when the filesystem was case-sensitive (Linux default, orWSGICaseSensitivity On) and preserved case when the filesystem was case-insensitive (Windows/macOS defaults, orWSGICaseSensitivity Off) — the opposite of the directive’s documented meaning and the function’s comment. A deployment that served the same script via paths differing only in case would have observed duplicate module loads on Windows/macOS and cache collisions on Linux; in practice almost no deployments mount scripts that way, so the bug has been latent.Fixed
mod_wsgi-expressso that supplying SSL certificate options (--ssl-certificate,--ssl-certificate-file,--ssl-certificate-key-file,--ssl-ca-certificate-file, or--ssl-certificate-chain-file) without also specifying--https-portnow fails with a clear error. Previously the SSL options were silently dropped from the generated Apache configuration because the HTTPSVirtualHostblock is only emitted when--https-portis set, leaving operators with a server that listened only on plain HTTP and no indication that their TLS configuration had been ignored.Fixed the shutdown stack-trace dump that fires after a request-timeout escalation in daemon mode so it now reports frames from the interpreter the offending request was running in. Previously the dump acquired the GIL via
PyGILState_Ensure(which attaches to the main interpreter) and called_PyThread_CurrentFrames, whose semantics changed in Python 3.12 to return frames for the current interpreter only. On Python 3.12 and later, requests served in a namedWSGIApplicationGroupsilently produced an empty or misleading dump because the dumping thread was attached to the main interpreter while the wedged worker was in a sub-interpreter. The escalation site now records which application group triggered the timeout and the dump scopes itself to that interpreter.Fixed the effective default of
WSGIMapHEADToGETwhen the directive is not set anywhere in the Apache configuration. The documented default isAuto, but the server-config initialiser left the underlying field at0(Off) rather than the-1“not set” sentinel that the per-request merge code expects, so the hardcodedAutofallback was unreachable and the effective default wasOff. The same initialiser also prevented a base-serverWSGIMapHEADToGETsetting from being inherited by a virtual host that did not set the directive itself, because the base-to-vhost merge treated every fresh vhost server config as having an explicitOff. The field is now initialised to the-1sentinel so the documentedAutodefault applies when the directive is omitted and a base-server setting propagates into vhosts that do not override it. Only manually-written Apache configurations are affected;mod_wsgi-expressalways emitsWSGIMapHEADToGETexplicitly (defaulting toAutovia--map-head-to-get) so generated configurations were never exposed to the latent default.