diff options
Diffstat (limited to 'docs/user-guides/registering-cleanup-code.rst')
-rw-r--r-- | docs/user-guides/registering-cleanup-code.rst | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/docs/user-guides/registering-cleanup-code.rst b/docs/user-guides/registering-cleanup-code.rst new file mode 100644 index 0000000..81889e5 --- /dev/null +++ b/docs/user-guides/registering-cleanup-code.rst @@ -0,0 +1,144 @@ +======================== +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 = '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): + for item in self.__iterable: + yield item + 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: + self.__callback(environ) + raise + return Generator2(result, self.__callback, environ) + +Note that for a successfully completed request, since the cleanup task will +be executed after the complete response has been written back to the +client, if an error occurs there will be no evidence of this in the +response seen by the client. As far as the client will be concerned +everything will look okay. The only indication of an error will be found 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 standard Python 'atexit' module can be used:: + + import atexit + + def cleanup(): + # Perform required cleanup task. + ... + + atexit.register(cleanup) + +Such a registered cleanup function will also be called if the 'Interpreter' +reload mechanism is enabled and the Python sub interpreter in which the +cleanup function was registered was destroyed. + +Note that although mod_wsgi will ensure that cleanup functions registered +using the 'atexit' module will be called correctly, this solution may not +be portable to all WSGI hosting solutions. + +Also be aware that although one can register a cleanup function to be +called on process shutdown, this is no absolute guarantee that it will be +called. This is because a process may crash, or it may be forcibly killed +off by Apache if it takes too long to shutdown normally. As a result, an +application should not be dependent on cleanup functions being called on +process shutdown and an application must have some means of detecting an +abnormal shutdown when it is started up and recover from it automatically. |