Registering Cleanup Code
This document describes how to go about registering callbacks to perform cleanup tasks at the end of a request and when an application process is being shutdown.
Cleanup At End Of Request
To perform a cleanup task at the end of a request a couple of different approaches can be used dependent on the requirements. The first approach entails wrapping the calling of a WSGI application within a Python ‘try’ block, with the cleanup code being triggered from the ‘finally’ block:
def _application(environ, start_response):
status = '200 OK'
output = b'Hello World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
def application(environ, start_response):
try:
return _application(environ, start_response)
finally:
# Perform required cleanup task.
...
This might even be factored into a convenient WSGI middleware component:
class ExecuteOnCompletion1:
def __init__(self, application, callback):
self.__application = application
self.__callback = callback
def __call__(self, environ, start_response):
try:
return self.__application(environ, start_response)
finally:
self.__callback(environ)
The WSGI environment passed in the ‘environ’ argument to the application could even be supplied to the cleanup callback as shown in case it needed to look at any configuration information or information passed back in the environment from the application.
The application would then be replaced with an instance of this class initialised with a reference to the original application and a suitable cleanup function:
def cleanup(environ):
# Perform required cleanup task.
...
application = ExecuteOnCompletion1(_application, cleanup)
Using this approach, the cleanup function will actually be called prior to the response content being consumed by mod_wsgi and written back to the client. As such, it is probably only suitable where a complete response is returned as an array of strings. It would not be suitable where a generator is being returned as the cleanup would be called prior to any strings being consumed from the generator. This would be problematic where the cleanup task was to close or delete some resource from which the generator was obtaining the response content.
In order to have the cleanup task only executed after the complete response has been consumed, it would be necessary to wrap the result of the application within an instance of a purpose built generator like object. This object needs to yield each item from the response in turn, and when this object is cleaned up by virtue of the ‘close()’ method being called, it should in turn call ‘close()’ on the result returned from the application if necessary, and then call the supplied cleanup callback:
class Generator2:
def __init__(self, iterable, callback, environ):
self.__iterable = iterable
self.__callback = callback
self.__environ = environ
def __iter__(self):
yield from self.__iterable
def close(self):
try:
if hasattr(self.__iterable, 'close'):
self.__iterable.close()
finally:
self.__callback(self.__environ)
class ExecuteOnCompletion2:
def __init__(self, application, callback):
self.__application = application
self.__callback = callback
def __call__(self, environ, start_response):
try:
result = self.__application(environ, start_response)
except Exception:
self.__callback(environ)
raise
return Generator2(result, self.__callback, environ)
Note that for a successfully completed request the cleanup task runs after the complete response has already been written back to the client. If the cleanup function itself raises an exception, the client will already have seen a successful response — the failure will be visible only in the Apache error log.
Both of the solutions above are not specific to mod_wsgi and should work with any WSGI hosting solution which complies with the WSGI specification.
Cleanup On Process Shutdown
To perform a cleanup task on shutdown of either an Apache child
process when using embedded mode of mod_wsgi, or of a daemon
process when using daemon mode of mod_wsgi, the recommended
mechanism is to register a callback with mod_wsgi.subscribe_shutdown():
import mod_wsgi
def cleanup(*args, **kwargs):
# Perform required cleanup task.
...
mod_wsgi.subscribe_shutdown(cleanup)
The function returns the callback so it can equivalently be used as a decorator:
import mod_wsgi
@mod_wsgi.subscribe_shutdown
def cleanup(*args, **kwargs):
# Perform required cleanup task.
...
mod_wsgi.subscribe_shutdown() is a shortcut for subscribing to
the single process_stopping event published by mod_wsgi when a
process is shutting down, and shares the calling convention used
for subscribers registered via mod_wsgi.subscribe_events(): the
event name is passed positionally and the event payload is passed
as keyword arguments. A callback that does not need either, like
the example above, can absorb both with *args, **kwargs.
To act on why the process is stopping (graceful shutdown, eviction,
request-time-limit eviction, and so on), declare shutdown_reason
as a keyword-only parameter:
def cleanup(name, *, shutdown_reason, **kwargs):
...
The standard Python atexit module can also be used:
import atexit
def cleanup():
# Perform required cleanup task.
...
atexit.register(cleanup)
However, atexit callbacks under mod_wsgi are not always
delivered. mod_wsgi relies on the Python interpreter’s normal
finalisation to drive atexit callbacks: CPython joins all
non-daemon threads first and then runs atexit callbacks as the
interpreter is torn down. Two situations make this unreliable:
If WSGIDestroyInterpreter is set to
Off, sub interpreters are not torn down at process shutdown so the finalisation path is never taken and registeredatexitfunctions will not run at all.Because Python joins non-daemon threads before running atexit callbacks, an application that creates background threads as non-daemon threads (when they should have been daemon threads) blocks finalisation indefinitely. Apache will then force-kill the process after its shutdown grace period and the atexit callbacks never fire.
By contrast, mod_wsgi.subscribe_shutdown() callbacks are
dispatched directly by mod_wsgi early in the shutdown sequence,
before non-daemon threads are joined and before any interpreter
destruction is attempted. They run regardless of
WSGIDestroyInterpreter and regardless of whether the
application has stuck non-daemon threads. New code should prefer
mod_wsgi.subscribe_shutdown(); existing atexit registrations
will keep working in the common case but should be migrated where
reliability matters.
Note that mod_wsgi.subscribe_shutdown() is a mod_wsgi-specific
extension and not portable to other WSGI hosting solutions; atexit
is portable but unreliable as described.
Also be aware that even with mod_wsgi.subscribe_shutdown(),
delivery of the callback is not absolutely guaranteed. The process
may crash, or it may be forcibly killed by Apache if it takes too
long to shut down. An application should therefore not be entirely
dependent on cleanup callbacks running, and should have some means
of detecting an abnormal shutdown when it next starts up and
recovering from it automatically.
mod_wsgi.subscribe_shutdown() is also inert in service-script
daemons (WSGIDaemonProcess threads=0): the publish point for
process_stopping lives in the per-request daemon main loop that
service scripts skip, so the registered callback is never invoked.
For service scripts, mod_wsgi pre-registers signal.signal(SIGTERM,
...) with a handler that raises SystemExit, so the supported
pattern is to wrap the script’s main loop in
try: ... except SystemExit: cleanup(). See the service-script
notes in Subscribing to Events for the full picture.