• No results found

Chapter 4 Programming Techniques and Caveats

4.9 External Dependencies and Libraries

4.9.2 Library Good Practice

Libraries, like modules, should always follow some basic rules of good practice. The most basic rule states that library functions should always return control to the caller in an orderly and properly documented manner. In particular, exit()is not

an acceptable way to handle errors.

Unfortunately, some libraries—particularly older ones that may have been intended only for command line programs—may violate this principle. Examples are com- mon in graphics libraries such as libjpeg.12Changing the library may pose a prob-

lem, but the conflict can be worked around by using setjmp/longjmp: typedef struct {

jmp_buf jmp; request_rec *r; } my_ctx;

We need to set libjpegto use longjmprather than exit()when it encounters a

fatal error:

static void jpeg_error_exit(j_common_ptr cinfo) {

my_ctx *ctx = (my_ctx*)cinfo->client_data; (*cinfo->err->output_message) (cinfo); longjmp(ctx->jmp, 1);

}

114 Chapter 4 • Programming Techniques and Caveats

We also need to register a function to generate error messages from the library:

static void jpeg_output_message(j_common_ptr cinfo) {

char buffer[JMSG_LENGTH_MAX];

my_ctx *ctx = (my_ctx*)cinfo->client_data ;

(*cinfo->err->format_message) (cinfo, buffer);

ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "JPEG Error: %s", buffer);

}

Now we need our handler to set up its own error handler with libjpeg: static int my_jpeg_handler(request_rec* r)

{

struct jpeg_compress_struct *cinfo; my_ctx *ctx;

struct jpeg_error_mgr *errptr

= apr_palloc(r->pool, sizeof(jpeg_error_mgr)); errptr->output_message = jpeg_output_message;

errptr->error_exit = jpeg_error_exit;

/* Register handler with libjpeg */ jpeg_std_error(errptr);

/* Create a jpeg context and register a cleanup on the pool * (cleanup function omitted for brevity)

*/

ctx = apr_palloc(r->pool, sizeof(my_ctx));

cinfo = apr_palloc(r->pool, sizeof(struct jpeg_compress_struct)); ctx->r = r; jpeg_create_compress(cinfo); cinfo->client_data = ctx; cinfo->err = errptr; apr_pool_cleanup_register(f->r->pool, cinfo, (apr_status_t(*)(void*))cjpeg_cleanup, apr_pool_cleanup_null);

/* Set up other fields of cinfo (omitted) */

/* Handle fatal errors from libjpeg */ if (setjmp(ctx->jmp)) {

ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Fatal Error in libjpeg");

return HTTP_INTERNAL_SERVER_ERROR ; }

/* Now do our processing with libjpeg, including (of course) * always checking for nonfatal errors.

* Fatal errors are now handled by longjmp setting the clock back * to our setjmp, so we'll log an error and return 500.

*/ }

C++ throw/catch is superficially a more elegant solution that accomplishes the

same thing, but in this author’s experience it doesn’t work so well in Apache. Your mileage may vary.

Thread Safety

When using third-party libraries, the module developer is responsible for ascer- taining whether the libraries are thread safe. If they are not, then your module will need to use a thread mutex for every library call (which may be prohibitively expensive). Alternatively, you can document the module as not being thread safe, and limit its use to the Prefork MPM. The best-known example that uses the lat- ter approach is PHP.

Some libraries—for example, the MySQL client library libmysqlclient—come

as more than one version: a “standard” version that is not thread safe, and an alter- native that is fully thread safe and reentrant. When using such a library, you should ensure your users always select the thread-safe version.

Initialization and Termination

Some libraries may require per-process initialization and termination. These tasks can be handled in various places in Apache, including the module hooks function. Unfortunately, the double start-up followed by forking of children makes this strat- egy complex and sometimes unsuitable. A library that is initialized before configu- ration will first be initialized, then terminated, then initialized a second time, after which child processes will be forked. If that behavior is acceptable with your library, then it’s a convenient way to handle initialization, because it makes the library avail- able to the configuration functions.

Naturally, you will also need to register any termination function on the pool. If the library has a function whose signature is not compatible with a pool cleanup, you’ll need to write a wrapper for it. Here’s a typical outline for initialization:

static void register_hooks(apr_pool_t *pool) {

my_lib_init() ;

apr_pool_cleanup_register(pool, NULL, my_lib_terminate, apr_pool_cleanup_null);

/* and of course register whatever this module exports */ }

When the start-stop-restart-fork process will cause trouble, the safest place to han- dle library initialization may be a function hooked to child_init. In this case, ini-

tialization happens after the fork but before entering operational mode. As with the other strategy, you’ll need to call library initialization and register the termination function, this time on the child pool. If any configuration functions require the library, however, you may need to do something more complex, such as saving the configuration data “raw” in the configuration phase and then running any neces- sary library functions on the raw input data after initializing the library in the

child_initphase.

Caution!

Bear in mind that your module may not be the only one to use the library. For most libraries, running initialization and global cleanup more than once does not pose a problem, so your module need not concern itself with this issue. If running either initialization or cleanup more than once will complicate life for the library, your module needs to be sensitive to whether another module is independently doing the same thing. One potential solution to this problem is to write a separate mini- module that specifically ensures that library functions are run exactly once.

Future Apache releases may provide a more elegant solution to this dilemma.

Library State Changes

If a library has global variables, any use of them is not thread safe.

A similar, more general issue arises when a library allows an application to change its state with global scope—for example, by registering callback functions for library events. This possibility is actually more than just a thread-safety issue, as it affects even the nonthreaded Prefork MPM.

To see how this problem arises, let’s consider a real-life example. The XML (and HTML) parsing library libxml2allows an application to register handlers for parse

errors. If your module uses such handlers, they should always be registered in the context of a parser that is owned by your module. However, mod_phpskimped on

this requirement and registered the handlers globally.13

Now, when another module uses libxml2 in processing a request and encounters

a parse error, the registered error handler is called. But that is PHP’s handler! Because it wasn’t a PHP request, there is no PHP context, and Apache will crash (segfault). PHP has become a cuckoo in the nest, and other modules had to take extra trouble to work around the bug if using the XML parser in a manner that might generate XML parse errors.