Show Contents Previous Page Next Page
Chapter 7 - Other Request Phases Registered Cleanups
Although the logging phase is the last official phase of the request cycle, there is one last place where modules can do work. This is the cleanup phase, during which any code registered as a cleanup handler is called to perform any per-transaction tidying up that the module may need to do.
Cleanup handlers can be installed in either of two ways. They can be installed by calling the request object's register_cleanup() method with a reference to a subroutine or method to invoke, or by using the PerlCleanupHandler directive to register a subroutine from within the server configuration file. Here are some examples:
# within a module file
$r->register_cleanup(sub { warn "server $$ done serving request\n" });
# within a configuration file
PerlModule Apache::Guillotine # make sure it's loaded
PerlCleanupHandler Apache::Guillotine::mopup()
There is not actually a cleanup phase per se. Instead, the C API provides
a callback mechanism for functions that are invoked just before their memory
pool is destroyed. A handful of Apache API methods use this mechanism underneath
for simple but important tasks, such as ensuring that files, directory handles,
and sockets are closed. In Chapter 10, C API Reference
Guide, Part I, you will see that the C version expects
a few more arguments, including a pool pointer.
There are actually two register_cleanup() methods: one associated with the Apache request object and the other associated with the Apache::Server object. The difference between the two is that handlers installed with the request object's method will be run when the request is done, while handlers installed with the server object's method will be run only when the server shuts down or restarts:
$r->register_cleanup(sub { "child $$ served another request" })
Apache->server->register_cleanup(sub { warn "server $$ restarting\n" });
We've already been using register_cleanup() indirectly with the Apache::File tmpfile() method, where it is used to unlink a temporary file at the end of the transaction even if the handler aborts prematurely. Another example can be found in CGI.pm, where a cleanup handler resets that module's package globals to a known state after each transaction. Here's the relevant code fragment:
Apache->request->register_cleanup(\&CGI::_reset_globals);
A more subtle use of registered cleanups is to perform delayed processing
on requests. For example, certain contributed mod_perl logging
modules, like Apache::DBILogger and Apache::Traffic, take
a bit more time to do their work than the standard logging modules do. Although
the overhead is small, it does lengthen the amount of time the user has to wait
before the browser's progress monitor indicates that the page is fully loaded.
In order to squeeze out the last ounce of performance, these modules defer the
real work to the cleanup phase. Because cleanups occur after the response is
finished, the user will not have to wait for the logging module to complete
its work.8
To take advantage of delayed processing, we can run the previous section's Apache::LogDBI module during the cleanup phase rather than the log phase. The change is simple. Just replace the PerlLogHandler directive with PerlCleanupHandler:
PerlCleanupHandler Apache::LogDBI
Because the cleanup handler can be used for post-transactional processing, the Perl API provides post_connection() as an alias for register_cleanup(). This can improve code readability somewhat:
sub handler {
shift->post_connection(\&logger);
}
Cleanup handlers follow the same calling conventions as other handlers. On entry, they receive a reference to an Apache object containing all the accumulated request and response information. They can return a status code if they wish to, but Apache will ignore it.
We've finally run out of transaction phases to talk about, so we turn our
attention to a more esoteric aspect of Apache, the proxy server API. Footnotes 8 Of course, moving the work out of the transaction and into
the cleanup phase just means that the child server or thread cannot serve another
request until this work is done. This only becomes a problem if the number of
concurrent requests exceeds the level that your server can handle. In this case,
the next incoming request may have to wait a little longer for the connection
to be established. You can decide if the subjective tradeoff is worth it. Show Contents Previous Page Next Page Copyright © 1999 by O'Reilly & Associates, Inc. |