Skip to content

Commit e5ee316

Browse files
Merge branch 'release/mod_wsgi-6.0.3'
2 parents 94f7a7e + 224a64a commit e5ee316

10 files changed

Lines changed: 266 additions & 128 deletions

File tree

docs/release-notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod_wsgi. See :doc:`project-status` for the version support policy.
88
.. toctree::
99
:maxdepth: 2
1010

11+
release-notes/version-6.0.3
1112
release-notes/version-6.0.2
1213
release-notes/version-6.0.1
1314
release-notes/version-6.0.0
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=============
2+
Version 6.0.3
3+
=============
4+
5+
Features Changed
6+
----------------
7+
8+
* `mod_wsgi-standalone` package has been updated to use `mod_wsgi-httpd-2.4.68.1`.
9+
Apache 2.4.68 includes a range of CVE fixes as detailed in the
10+
[Apache HTTP Server 2.4.68 changelog](https://dlcdn.apache.org/httpd/CHANGES_2.4.68).

docs/user-guides/application-issues.rst

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,14 +344,88 @@ matches the default for many applications, including Django when
344344
``TIME_ZONE`` is left at its ``UTC`` default. If the application uses a
345345
different timezone, set ``TZ`` to that same timezone instead.
346346

347-
Note that there is no Apache log format directive which will solve this by
348-
forcing UTC. Both ``ErrorLogFormat`` and ``CustomLog`` / ``LogFormat``
349-
format their ``%t`` timestamps from the process's local time, derived from
350-
``TZ``; even the compact ISO 8601 forms such as ``%{cu}t`` use local time,
351-
not UTC. The ``%{cuz}t`` form (Apache 2.4.58 and later) at least appends
352-
the numeric timezone offset, so lines stamped in different timezones can
353-
be told apart rather than silently misread, but it does not make them
354-
consistent.
347+
Where you are not able to change how Apache is started, a log format
348+
directive offers a partial mitigation, though not a cure. It is worth
349+
being clear about its limits, because reaching for the log configuration
350+
is the natural first instinct. No ``ErrorLogFormat`` or ``CustomLog`` /
351+
``LogFormat`` option can force these timestamps to a fixed zone such as
352+
UTC; ``%t`` is always formatted from the emitting process's local time,
353+
derived from ``TZ``. What a log format *can* do is make the timezone of
354+
each line explicit, so that lines stamped by processes in different
355+
timezones can be told apart rather than silently misread.
356+
357+
In an ``ErrorLogFormat`` the ``%t`` field accepts a small set of single
358+
letter options inside the braces:
359+
360+
* ``%t`` produces the default ``ctime`` style, ``Fri Jun 05 08:42:44 2026``.
361+
* ``%{u}t`` is the same but with microseconds. This is what the Apache
362+
default ``ErrorLogFormat`` uses.
363+
* the ``c`` option switches to the compact ISO 8601 form,
364+
``2026-06-05 08:42:36``.
365+
* the ``z`` option (Apache 2.4.58 and later) appends the numeric timezone
366+
offset, ``+1000``.
367+
368+
The options combine, so ``%{cu}t`` is compact ISO 8601 with microseconds
369+
but still no offset, and ``%{cuz}t`` is that with the offset added on the
370+
end.
371+
372+
To keep the familiar default layout and simply add the timezone offset,
373+
take the Apache default ``ErrorLogFormat`` and change its ``%{u}t`` field
374+
to ``%{uz}t``::
375+
376+
ErrorLogFormat "[%{uz}t] [%-m:%l] [pid %P:tid %T] %7F: %E: [client\ %a] %M% ,\ referer\ %{Referer}i"
377+
378+
A line then looks like the following, identical to the default apart from
379+
the trailing ``+1000``::
380+
381+
[Fri Jun 05 08:42:29.986168 2026 +1000] [mpm_event:notice] [pid 37192:tid 140704575428864] AH00489: ...
382+
383+
If you prefer the compact ISO 8601 timestamp, use ``%{cuz}t`` instead::
384+
385+
ErrorLogFormat "[%{cuz}t] [%-m:%l] [pid %P:tid %T] %7F: %E: [client\ %a] %M% ,\ referer\ %{Referer}i"
386+
387+
[2026-06-05 08:42:36.978307 +1000] [mpm_event:notice] [pid 37243:tid 140704575428864] AH00489: ...
388+
389+
A word of caution about the syntax. The ``c``, ``u`` and ``z`` letters are
390+
options, not ``strftime`` conversions, and must appear directly inside the
391+
braces with no leading ``%``. If you write ``%{%cuz}t`` instead of
392+
``%{cuz}t``, the leading ``%`` switches the field into ``strftime`` mode,
393+
where ``%c`` expands to the C library's locale date and time and the
394+
remaining ``uz`` is copied through literally, producing nonsense such as
395+
``Fri Jun 5 08:42:25 2026uz``.
396+
397+
This distinction also matters across platforms. The ``c``, ``u`` and ``z``
398+
options are formatted by Apache itself and behave identically on Linux,
399+
macOS and Windows. The ``strftime`` forms, such as ``%{%d/%b/%Y %T}t``,
400+
are passed to the platform C library's ``strftime(3)``, whose set of
401+
supported conversions varies; Windows in particular omits or alters
402+
several of them, with ``%z`` for example yielding a timezone name rather
403+
than a numeric offset. Preferring ``%{cuz}t`` over a hand written
404+
``strftime`` string therefore also gives consistent output everywhere.
405+
406+
Adding the offset lets the two timezones be distinguished, but it does not
407+
make them consistent. The only way to have every process agree remains to
408+
start the Apache parent process with the same ``TZ`` the application uses,
409+
as described above.
410+
411+
All of the above concerns the error log specifically. The access log does
412+
not suffer the same silent confusion, for two reasons. First, its default
413+
format already includes the timezone offset: the standard ``common`` and
414+
``combined`` ``LogFormat`` definitions use ``%t``, which mod_log_config
415+
renders in Common Log Format, with the offset built in::
416+
417+
::1 - - [05/Jun/2026:09:07:53 +1000] "GET / HTTP/1.1" 200 12
418+
419+
Second, in daemon mode the access log is written by the Apache child worker
420+
process, since that is where mod_log_config runs as it proxies the
421+
request. The worker keeps the Apache parent's ``TZ`` and never runs the
422+
application, so its timezone cannot be changed by the application's call to
423+
``tzset()``. Access log lines therefore stay in one consistent timezone,
424+
unlike error log lines which mix the daemon process's application timezone
425+
with the worker process's timezone. The access log timestamp is still
426+
local time rather than UTC, so the parent-process ``TZ`` fix is still what
427+
to reach for if UTC is required, but the access log never exhibits the
428+
silent, hard to spot divergence that the error log does.
355429

356430
Be aware also that this only addresses divergence between processes. If a
357431
single daemon process hosts multiple sub interpreters that set different

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,6 @@ def _version():
456456
entry_points = { 'console_scripts':
457457
['mod_wsgi-express = mod_wsgi.express.cli:main'],},
458458
zip_safe = False,
459-
install_requires = standalone and ['mod_wsgi-httpd==2.4.67.1'] or [],
459+
install_requires = standalone and ['mod_wsgi-httpd==2.4.68.1'] or [],
460460
python_requires='>=3.10',
461461
)

src/express/cli.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,33 @@ def cmd_start_server(params):
6767
httpd_arguments.extend(['-f', config['httpd_conf']])
6868
httpd_arguments.extend(['-DONE_PROCESS'])
6969

70-
os.environ['MOD_WSGI_MODULES_DIRECTORY'] = config['modules_directory']
71-
72-
subprocess.call([executable]+httpd_arguments)
73-
74-
sys.exit(0)
70+
# On Windows httpd shares our console, so a Ctrl-C delivers a
71+
# CTRL_C_EVENT to the whole console process group and httpd runs
72+
# its own graceful shutdown. We must not let the same Ctrl-C kill
73+
# this launcher first, or it returns to the shell while httpd is
74+
# still tearing down (and dumps a KeyboardInterrupt traceback). So
75+
# absorb our own interrupt and keep waiting until httpd exits.
76+
#
77+
# A first Ctrl-C is treated as "you should have received the
78+
# console event, shutting down gracefully, I will wait". If httpd
79+
# did not actually receive the event (for example a git-bash pty
80+
# bridge that does not forward it to the child), a second Ctrl-C
81+
# escalates to terminating it so we cannot hang indefinitely.
82+
83+
process = subprocess.Popen([executable]+httpd_arguments, env=environ)
84+
85+
interrupts = 0
86+
87+
while True:
88+
try:
89+
process.wait()
90+
break
91+
except KeyboardInterrupt:
92+
interrupts += 1
93+
if interrupts >= 2:
94+
process.terminate()
95+
96+
sys.exit(process.returncode)
7597

7698
else:
7799
executable = posixpath.join(config['server_root'], 'apachectl')

src/express/platform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def default_run_group():
8282

8383
def find_program(names, default=None, paths=[]):
8484
for name in names:
85-
for path in os.environ['PATH'].split(':') + paths:
85+
for path in os.environ['PATH'].split(os.pathsep) + paths:
8686
program = posixpath.join(path, name)
8787
if os.path.exists(program):
8888
return program

src/express/reloader.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ def track_changes(path):
9797
_files.append(path)
9898

9999
def start_reloader(interval=1.0):
100+
# The reloader triggers a restart by sending SIGINT to this process. On
101+
# Windows os.kill() cannot deliver SIGINT to ourselves: any signal other
102+
# than CTRL_C_EVENT/CTRL_BREAK_EVENT is implemented as an unconditional
103+
# TerminateProcess, so the reloader would hard kill the server rather than
104+
# trigger a graceful restart. Disable it on Windows.
105+
if os.name == 'nt':
106+
prefix = 'monitor (pid=%d):' % os.getpid()
107+
print('%s Source code reloading is not supported on Windows.' % prefix,
108+
file=sys.stderr)
109+
return
110+
100111
global _interval
101112
if interval < _interval:
102113
_interval = interval

src/express/server.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,17 @@ def setup_server(command, args, options):
255255
options['access_log_file'] = posixpath.join(
256256
options['log_directory'], options['access_log_name'])
257257
else:
258-
try:
259-
with open('/dev/stdout', 'w'):
260-
pass
261-
except IOError:
262-
options['access_log_file'] = '|%s' % find_program(
263-
['tee'], default='tee')
258+
if os.name == 'nt':
259+
options['access_log_file'] = 'CON'
264260
else:
265-
options['access_log_file'] = '/dev/stdout'
261+
try:
262+
with open('/dev/stdout', 'w'):
263+
pass
264+
except IOError:
265+
options['access_log_file'] = '|%s' % find_program(
266+
['tee'], default='tee')
267+
else:
268+
options['access_log_file'] = '/dev/stdout'
266269

267270
if options['access_log_format']:
268271
if options['access_log_format'] in ('common', 'combined'):

src/server/wsgi_version.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525

2626
#define MOD_WSGI_MAJORVERSION_NUMBER 6
2727
#define MOD_WSGI_MINORVERSION_NUMBER 0
28-
#define MOD_WSGI_MICROVERSION_NUMBER 2
29-
#define MOD_WSGI_VERSION_STRING "6.0.2"
28+
#define MOD_WSGI_MICROVERSION_NUMBER 3
29+
#define MOD_WSGI_VERSION_STRING "6.0.3.dev1"
3030

3131
/* ------------------------------------------------------------------------- */
3232

0 commit comments

Comments
 (0)