You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Sander Temme <sa...@temme.net> on 2004/05/05 02:49:24 UTC

Sample code for IPC in modules

Hi all,

Following some questions on the apache-modules list, I whipped up a 
quick module for Apache 2.0 to hopefully demonstrate how it's done. I'm 
including the module code below: please tell me whether I'm smoking 
crack before I post this on apache-modules.

Thanks for your thoughts,

Sander

/*
**  mod_example_ipc.c -- Apache sample example_ipc module
**  [Autogenerated via ``apxs -n example_ipc -g'']
**
**  To play with this sample module first compile it into a
**  DSO file and install it into Apache's modules directory
**  by running:
**
**    $ apxs -c -i mod_example_ipc.c
**
**  Then activate it in Apache's httpd.conf file for instance
**  for the URL /example_ipc in as follows:
**
**    #   httpd.conf
**    LoadModule example_ipc_module modules/mod_example_ipc.so
**    <Location /example_ipc>
**    SetHandler example_ipc
**    </Location>
**
**  Then after restarting Apache via
**
**    $ apachectl restart
**
**  The module allocates a counter in shared memory, which is 
incremented
**  by the request handler under a mutex. After installation, hit the 
server
**  with ab at various concurrency levels to see how mutex contention 
affects
**  server performance.
*/

#include <sys/types.h>
#include <unistd.h>

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "ap_config.h"

#include "apr_strings.h"

#define HTML_HEADER "<html>\n<head>\n<title>Mod_example_IPC Status Page 
" \
                     "</title>\n</head>\n<body>\n<h1>Mod_example_IPC 
Status</h1>\n"
#define HTML_FOOTER "</body>\n</html>\n"

/* Number of microseconds to camp out on the mutex */
#define CAMPOUT 10
/* Maximum number of times we camp out before giving up */
#define MAXCAMP 10

apr_shm_t *exipc_shm;
char *shmfilename;
apr_global_mutex_t *exipc_mutex;
char *mutexfilename;

typedef struct exipc_data {
     apr_uint64_t counter;
     /* More fields if necessary */
} exipc_data;

/*
  * This routine is called in the parent, so we'll set up the shared
  * memory segment and mutex here.
  */

static int exipc_post_config(apr_pool_t *pconf, apr_pool_t *plog,
                              apr_pool_t *ptemp, server_rec *s)
{
     void *data; /* These two help ensure that we only init once. */
     const char *userdata_key = "example_ipc_init_module";
     apr_status_t rs;
     exipc_data *base;


     /*
      * The following checks if this routine has been called before.
      * This is necessary because the parent process gets initialized
      * a couple of times as the server starts up, and we don't want
      * to create any more mutexes and shared memory segments than
      * we're actually going to use.
      */
     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
     if (!data) {
         apr_pool_userdata_set((const void *) 1, userdata_key,
                               apr_pool_cleanup_null, s->process->pool);
         return OK;
     } /* Kilroy was here */

     /* Create the shared memory segment */

     /*
      * Create a unique filename using our pid. This information is
      * stashed in the global variable so the children inherit it.
      * TODO get the location from the environment $TMPDIR or somesuch.
      */
     shmfilename = apr_psprintf(pconf, "/tmp/httpd_shm.%ld", (long 
int)getpid());

     /* Now create that segment */
     rs = apr_shm_create(&exipc_shm, sizeof(exipc_data),
                         (const char *) shmfilename, pconf);
     if (rs != APR_SUCCESS) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
                      "Failed to create shared memory segment on file 
%s",
                      shmfilename);
         return HTTP_INTERNAL_SERVER_ERROR;
     }

     /* Created it, now let's zero it out */
     base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
     base->counter = 0;

     /* Create global mutex */

     /*
      * Create another unique filename to lock upon. Note that
      * depending on OS and locking mechanism of choice, the file
      * may or may not be actually created.
      */
     mutexfilename = apr_psprintf(pconf, "/tmp/httpd_mutex.%ld",
                                  (long int) getpid());

     rs = apr_global_mutex_create(&exipc_mutex, (const char *) 
mutexfilename,
                                  APR_LOCK_DEFAULT, pconf);
     if (rs != APR_SUCCESS) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
                      "Failed to create mutex on file %s",
                      mutexfilename);
         return HTTP_INTERNAL_SERVER_ERROR;
     }

     return OK;
}

/*
  * This routine gets called when a child exits. It detaches from the
  * shared memory segment. (is this necessary?)
  */

static apr_status_t exipc_child_exit(void *data)
{
     apr_status_t rs;

     rs = apr_shm_detach(exipc_shm);
     return rs;
}

/*
  * This routine gets called when a child inits. We use it to attach
  * to the shared memory segment, and reinitialize the mutex.
  */

static void exipc_child_init(apr_pool_t *p, server_rec *s)
{
     apr_status_t rs;

     /*
      * Attach to the shared memory segment. Note that we're
      * reusing the global variable here: the data in that
      * may have meaning only to the parent process that
      * created the segment. We're identifying the segment
      * through the filename (which we inherited from the parent.
      */
     rs = apr_shm_attach(&exipc_shm, (const char *) shmfilename, p);
     if (rs != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
                      "Failed to attach to shared memory segment on file 
%s",
                      shmfilename);
          /* There's really nothing else we can do here, since
           * This routine doesn't return a status. */
          exit(1); /* Ugly, but what else? */
      }

      /*
       * Re-open the mutex for the child. Note we're also reusing
       * the mutex pointer global here.
       */
      rs = apr_global_mutex_child_init(&exipc_mutex,
                                       (const char *) mutexfilename,
                                       p);
      if (rs != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
                      "Failed to reopen mutex on file %s",
                      shmfilename);
          /* There's really nothing else we can do here, since
           * This routine doesn't return a status. */
          exit(1); /* Ugly, but what else? */
      }

     /* Install the exit routine as cleanup for the pool. There
      * is no hook for this.
      */
     apr_pool_cleanup_register(p, s, exipc_child_exit, exipc_child_exit);
}

/* The sample content handler */
static int exipc_handler(request_rec *r)
{
     int gotlock = 0;
     int camped;
     apr_time_t startcamp;
     apr_int64_t timecamped;
     apr_status_t rs;
     exipc_data *base;

     if (strcmp(r->handler, "example_ipc")) {
         return DECLINED;
     }

     /*
      * The main function of the handler, aside from sending the
      * status page to the client, is to increment the counter in
      * the shared memory segment. This action needs to be mutexed
      * out using the global mutex.
      */

     /* First, acquire the lock */
     for (camped = 0, timecamped = 0; camped < MAXCAMP; camped++) {
         rs = apr_global_mutex_trylock(exipc_mutex);
         if (APR_STATUS_IS_EBUSY(rs)) {
             apr_sleep(CAMPOUT);
         } else if (APR_STATUS_IS_SUCCESS(rs)) {
             gotlock = 1;
             break; /* Get out of the loop */
         } else if (APR_STATUS_IS_ENOTIMPL(rs)) {
             /* If it's not implemented, just hang in the mutex. */
             startcamp = apr_time_now();
             rs = apr_global_mutex_lock(exipc_mutex);
             timecamped = (apr_int64_t) (apr_time_now() - startcamp);
             if (APR_STATUS_IS_SUCCESS(rs)) {
                 gotlock = 1;
                 break;
             } else {
                 /* Some error, log and bail */
                 ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server,
                              "Child %ld failed to acquire lock",
                              (long int)getpid());
                 return HTTP_INTERNAL_SERVER_ERROR;
             }
         } else {
             /* Some other error, log and bail */
             ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server,
                          "Child %ld failed to try and acquire lock",
                          (long int)getpid());
             return HTTP_INTERNAL_SERVER_ERROR;

         }
         timecamped += CAMPOUT;
         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_NOTICE,
                      0, r->server, "Child %ld camping out on mutex for 
%d microseconds",
                      (long int) getpid(), camped * CAMPOUT);
     }

     /* Sleep for a millisecond to make it a little harder for
      * httpd children to acquire the lock.
      */
     apr_sleep(1000);

     r->content_type = "text/html";

     if (!r->header_only) {
         ap_rputs(HTML_HEADER, r);
         if (gotlock) {
             /* Increment the counter */
             base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
             base->counter++;
             /* Send a page with our pid and the new value of the 
counter. */
             ap_rprintf(r, "<p>Lock acquired after camping out for %ld 
microseoncds.</p>\n",
                        (long int) timecamped);
             ap_rputs("<table border=\"1\">\n", r);
             ap_rprintf(r, "<tr><td>Child pid:</td><td>%d</td></tr>\n",
                        (int) getpid());
             ap_rprintf(r, "<tr><td>Counter:</td><td>%u</td></tr>\n",
                        (unsigned int)base->counter);
             ap_rputs("</table>\n", r);
         } else {
             /*
              * Send a page saying that we couldn't get the lock. Don't 
say
              * what the counter is, because without the lock the value 
could
              * race.
              */
              ap_rprintf(r, "<p>Child %d failed to acquire lock "
                         "after camping out for %d microseconds.</p>\n",
                         (int) getpid(), (int) timecamped);
         }
         ap_rputs(HTML_FOOTER, r);
     } /* r->header_only */

     /* Release the lock */
     if (gotlock)
         rs = apr_global_mutex_unlock(exipc_mutex);
     /* Swallowing the result because what are we going to do with it at
      * this stage?
      */

     return OK;
}

static void exipc_register_hooks(apr_pool_t *p)
{
     ap_hook_post_config(exipc_post_config, NULL, NULL, APR_HOOK_MIDDLE);
     ap_hook_child_init(exipc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
     ap_hook_handler(exipc_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA example_ipc_module = {
     STANDARD20_MODULE_STUFF,
     NULL,                  /* create per-dir    config structures */
     NULL,                  /* merge  per-dir    config structures */
     NULL,                  /* create per-server config structures */
     NULL,                  /* merge  per-server config structures */
     NULL,                  /* table of config file commands       */
     exipc_register_hooks   /* register hooks                      */
};


-- 
sander@temme.net              http://www.temme.net/sander/
PGP FP: 51B4 8727 466A 0BC3 69F4  B7B8 B2BE BC40 1529 24AF

Re: Sample code for IPC in modules

Posted by Mark Wolgemuth <ma...@employease.com>.
(see note on hup cleanup below)
On May 5, 2004, at 2:51 AM, Sander Temme wrote:

> Hi Mark,
>
> Thanks for your observations.
>
> On May 4, 2004, at 7:18 PM, mark wrote:
>
>> 2)
>> Dettach is never needed. However, depending on desired results, it is 
>> usually desireable to perform a destroy when a HUP signal is sent, so 
>> that it gets created fresh by post_config
>>
>> I've run into the strange errors under high load where newly forked 
>> children startup thinking they are attached to the inherited shm seg, 
>> but are in fact attached to some anonymous new segment. No error is 
>> produced, but obviously it's a catastrophic situation.
>
> Yeah, that would be Bad. However, how does one hook into the SIGHUP 
> handler? AFAIK, httpd has its own signal handlers that do stuff like 
> restarts and graceful, and if I registered another handler, I would 
> overrule the one that httpd sets. Or is there a provision for a chain 
> of signal handlers?
>

You don't really need to worry about the SIGHUP handler, just tie a 
cleanup function to the pool used to create
it in post_config. This will be the process pool of the parent, and its 
cleanups get run after all the children exit on a restart. It works for 
me.

static apr_status_t
shm_cleanup_wrapper(void *unused) {
	int rv;
	if (shm_seg)
		rv = apr_shm_destroy(shm_seg);
	else
		rv = APR_EGENERAL;
	return rv;
}

then in post_config:

apr_pool_cleanup_register(pool, NULL, shm_cleanup_wrapper, 
apr_pool_cleanup_null);

... where pool is the first parameter to post_config (and used to 
create shmseg);



> I put the new version at 
> <http://apache.org/~sctemme/mod_example_ipc.c> to save on e-mail 
> bandwidth.
>
> Thanks again,
>
> S.
>
> -- 
> sander@temme.net              http://www.temme.net/sander/
> PGP FP: 51B4 8727 466A 0BC3 69F4  B7B8 B2BE BC40 1529 24AF


Re: Sample code for IPC in modules

Posted by Geoffrey Young <ge...@modperlcookbook.org>.
> I put the new version at <http://apache.org/~sctemme/mod_example_ipc.c>
> to save on e-mail bandwidth.

if you're interested in this kind of thing, I've wrapped up mod_example_ipc
in an Apache-Test tarball:

  http://perl.apache.org/~geoff/mod_example-ipc.tar.gz

for no particular reason except that I know you were at my apachecon talk on
Apache-Test but I didn't cover C module integration at all.

fwiw.

--Geoff

Re: Sample code for IPC in modules

Posted by Sander Temme <sa...@temme.net>.
Hi Mark,

Thanks for your observations.

On May 4, 2004, at 7:18 PM, mark wrote:

> Your attach logic should work, however it raises privilege issues 
> because the children run as a different user (nobody or www, etc) the 
> than the process running the create (root). I had problems when I was 
> doing it that way, worked

I have to admit I hadn't tested as root, and it does exhibit permission 
problems on linux and darwin as well. Omitting the attach solves that 
problem, and I also got reacquainted with 
unixd_set_global_mutex_perms(). So, both of those are working now.

> 2)
> Dettach is never needed. However, depending on desired results, it is 
> usually desireable to perform a destroy when a HUP signal is sent, so 
> that it gets created fresh by post_config
>
> I've run into the strange errors under high load where newly forked 
> children startup thinking they are attached to the inherited shm seg, 
> but are in fact attached to some anonymous new segment. No error is 
> produced, but obviously it's a catastrophic situation.

Yeah, that would be Bad. However, how does one hook into the SIGHUP 
handler? AFAIK, httpd has its own signal handlers that do stuff like 
restarts and graceful, and if I registered another handler, I would 
overrule the one that httpd sets. Or is there a provision for a chain 
of signal handlers?

I put the new version at <http://apache.org/~sctemme/mod_example_ipc.c> 
to save on e-mail bandwidth.

Thanks again,

S.

-- 
sander@temme.net              http://www.temme.net/sander/
PGP FP: 51B4 8727 466A 0BC3 69F4  B7B8 B2BE BC40 1529 24AF

Re: Sample code for IPC in modules

Posted by mark <ma...@node.to>.
On May 4, 2004, at 8:49 PM, Sander Temme wrote:

> Hi all,
>
> Following some questions on the apache-modules list, I whipped up a 
> quick module for Apache 2.0 to hopefully demonstrate how it's done. 
> I'm including the module code below: please tell me whether I'm 
> smoking crack before I post this on apache-modules.
>
> Thanks for your thoughts,

A few notes, based on my experience using shm in 2.0 and observations 
of mod_auth_digest's and mod_ssl's implementation.

1)
No  attach logic is needed provided you keep the base address from 
apr_shm_create in a global var. It will get inherited when the children 
are forked. I separate shm handling out into a separate file that is 
linked in. It declares a static global base, and provides accessor 
functions to make that work cleanly.

Your attach logic should work, however it raises privilege issues 
because the children run as a different user (nobody or www, etc) the 
than the process running the create (root). I had problems when I was 
doing it that way, worked ok on freebsd and linux, but had strange 
behaviour on solaris. Attach is primarily designed to be used in cases 
where a totally separate process wants access to a piece of shared 
memory, not a forked one.

2)
Dettach is never needed. However, depending on desired results, it is 
usually desireable to perform a destroy when a HUP signal is sent, so 
that it gets created fresh by post_config

I've run into the strange errors under high load where newly forked 
children startup thinking they are attached to the inherited shm seg, 
but are in fact attached to some anonymous new segment. No error is 
produced, but obviously it's a catastrophic situation.

>
> Sander
>
> /*
> **  mod_example_ipc.c -- Apache sample example_ipc module
> **  [Autogenerated via ``apxs -n example_ipc -g'']
> **
> **  To play with this sample module first compile it into a
> **  DSO file and install it into Apache's modules directory
> **  by running:
> **
> **    $ apxs -c -i mod_example_ipc.c
> **
> **  Then activate it in Apache's httpd.conf file for instance
> **  for the URL /example_ipc in as follows:
> **
> **    #   httpd.conf
> **    LoadModule example_ipc_module modules/mod_example_ipc.so
> **    <Location /example_ipc>
> **    SetHandler example_ipc
> **    </Location>
> **
> **  Then after restarting Apache via
> **
> **    $ apachectl restart
> **
> **  The module allocates a counter in shared memory, which is 
> incremented
> **  by the request handler under a mutex. After installation, hit the 
> server
> **  with ab at various concurrency levels to see how mutex contention 
> affects
> **  server performance.
> */
>
> #include <sys/types.h>
> #include <unistd.h>
>
> #include "httpd.h"
> #include "http_config.h"
> #include "http_log.h"
> #include "http_protocol.h"
> #include "ap_config.h"
>
> #include "apr_strings.h"
>
> #define HTML_HEADER "<html>\n<head>\n<title>Mod_example_IPC Status 
> Page " \
>                     "</title>\n</head>\n<body>\n<h1>Mod_example_IPC 
> Status</h1>\n"
> #define HTML_FOOTER "</body>\n</html>\n"
>
> /* Number of microseconds to camp out on the mutex */
> #define CAMPOUT 10
> /* Maximum number of times we camp out before giving up */
> #define MAXCAMP 10
>
> apr_shm_t *exipc_shm;
> char *shmfilename;
> apr_global_mutex_t *exipc_mutex;
> char *mutexfilename;
>
> typedef struct exipc_data {
>     apr_uint64_t counter;
>     /* More fields if necessary */
> } exipc_data;
>
> /*
>  * This routine is called in the parent, so we'll set up the shared
>  * memory segment and mutex here.
>  */
>
> static int exipc_post_config(apr_pool_t *pconf, apr_pool_t *plog,
>                              apr_pool_t *ptemp, server_rec *s)
> {
>     void *data; /* These two help ensure that we only init once. */
>     const char *userdata_key = "example_ipc_init_module";
>     apr_status_t rs;
>     exipc_data *base;
>
>
>     /*
>      * The following checks if this routine has been called before.
>      * This is necessary because the parent process gets initialized
>      * a couple of times as the server starts up, and we don't want
>      * to create any more mutexes and shared memory segments than
>      * we're actually going to use.
>      */
>     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
>     if (!data) {
>         apr_pool_userdata_set((const void *) 1, userdata_key,
>                               apr_pool_cleanup_null, s->process->pool);
>         return OK;
>     } /* Kilroy was here */
>
>     /* Create the shared memory segment */
>
>     /*
>      * Create a unique filename using our pid. This information is
>      * stashed in the global variable so the children inherit it.
>      * TODO get the location from the environment $TMPDIR or somesuch.
>      */
>     shmfilename = apr_psprintf(pconf, "/tmp/httpd_shm.%ld", (long 
> int)getpid());
>
>     /* Now create that segment */
>     rs = apr_shm_create(&exipc_shm, sizeof(exipc_data),
>                         (const char *) shmfilename, pconf);
>     if (rs != APR_SUCCESS) {
>         ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
>                      "Failed to create shared memory segment on file 
> %s",
>                      shmfilename);
>         return HTTP_INTERNAL_SERVER_ERROR;
>     }
>
>     /* Created it, now let's zero it out */
>     base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
>     base->counter = 0;
>
>     /* Create global mutex */
>
>     /*
>      * Create another unique filename to lock upon. Note that
>      * depending on OS and locking mechanism of choice, the file
>      * may or may not be actually created.
>      */
>     mutexfilename = apr_psprintf(pconf, "/tmp/httpd_mutex.%ld",
>                                  (long int) getpid());
>
>     rs = apr_global_mutex_create(&exipc_mutex, (const char *) 
> mutexfilename,
>                                  APR_LOCK_DEFAULT, pconf);
>     if (rs != APR_SUCCESS) {
>         ap_log_error(APLOG_MARK, APLOG_ERR, rs, s,
>                      "Failed to create mutex on file %s",
>                      mutexfilename);
>         return HTTP_INTERNAL_SERVER_ERROR;
>     }
>
>     return OK;
> }
>
> /*
>  * This routine gets called when a child exits. It detaches from the
>  * shared memory segment. (is this necessary?)
>  */
>
> static apr_status_t exipc_child_exit(void *data)
> {
>     apr_status_t rs;
>
>     rs = apr_shm_detach(exipc_shm);
>     return rs;
> }
>
> /*
>  * This routine gets called when a child inits. We use it to attach
>  * to the shared memory segment, and reinitialize the mutex.
>  */
>
> static void exipc_child_init(apr_pool_t *p, server_rec *s)
> {
>     apr_status_t rs;
>
>     /*
>      * Attach to the shared memory segment. Note that we're
>      * reusing the global variable here: the data in that
>      * may have meaning only to the parent process that
>      * created the segment. We're identifying the segment
>      * through the filename (which we inherited from the parent.
>      */
>     rs = apr_shm_attach(&exipc_shm, (const char *) shmfilename, p);
>     if (rs != APR_SUCCESS) {
>          ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
>                      "Failed to attach to shared memory segment on 
> file %s",
>                      shmfilename);
>          /* There's really nothing else we can do here, since
>           * This routine doesn't return a status. */
>          exit(1); /* Ugly, but what else? */
>      }
>
>      /*
>       * Re-open the mutex for the child. Note we're also reusing
>       * the mutex pointer global here.
>       */
>      rs = apr_global_mutex_child_init(&exipc_mutex,
>                                       (const char *) mutexfilename,
>                                       p);
>      if (rs != APR_SUCCESS) {
>          ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s,
>                      "Failed to reopen mutex on file %s",
>                      shmfilename);
>          /* There's really nothing else we can do here, since
>           * This routine doesn't return a status. */
>          exit(1); /* Ugly, but what else? */
>      }
>
>     /* Install the exit routine as cleanup for the pool. There
>      * is no hook for this.
>      */
>     apr_pool_cleanup_register(p, s, exipc_child_exit, 
> exipc_child_exit);
> }
>
> /* The sample content handler */
> static int exipc_handler(request_rec *r)
> {
>     int gotlock = 0;
>     int camped;
>     apr_time_t startcamp;
>     apr_int64_t timecamped;
>     apr_status_t rs;
>     exipc_data *base;
>
>     if (strcmp(r->handler, "example_ipc")) {
>         return DECLINED;
>     }
>
>     /*
>      * The main function of the handler, aside from sending the
>      * status page to the client, is to increment the counter in
>      * the shared memory segment. This action needs to be mutexed
>      * out using the global mutex.
>      */
>
>     /* First, acquire the lock */
>     for (camped = 0, timecamped = 0; camped < MAXCAMP; camped++) {
>         rs = apr_global_mutex_trylock(exipc_mutex);
>         if (APR_STATUS_IS_EBUSY(rs)) {
>             apr_sleep(CAMPOUT);
>         } else if (APR_STATUS_IS_SUCCESS(rs)) {
>             gotlock = 1;
>             break; /* Get out of the loop */
>         } else if (APR_STATUS_IS_ENOTIMPL(rs)) {
>             /* If it's not implemented, just hang in the mutex. */
>             startcamp = apr_time_now();
>             rs = apr_global_mutex_lock(exipc_mutex);
>             timecamped = (apr_int64_t) (apr_time_now() - startcamp);
>             if (APR_STATUS_IS_SUCCESS(rs)) {
>                 gotlock = 1;
>                 break;
>             } else {
>                 /* Some error, log and bail */
>                 ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server,
>                              "Child %ld failed to acquire lock",
>                              (long int)getpid());
>                 return HTTP_INTERNAL_SERVER_ERROR;
>             }
>         } else {
>             /* Some other error, log and bail */
>             ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server,
>                          "Child %ld failed to try and acquire lock",
>                          (long int)getpid());
>             return HTTP_INTERNAL_SERVER_ERROR;
>
>         }
>         timecamped += CAMPOUT;
>         ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_NOTICE,
>                      0, r->server, "Child %ld camping out on mutex for 
> %d microseconds",
>                      (long int) getpid(), camped * CAMPOUT);
>     }
>
>     /* Sleep for a millisecond to make it a little harder for
>      * httpd children to acquire the lock.
>      */
>     apr_sleep(1000);
>
>     r->content_type = "text/html";
>
>     if (!r->header_only) {
>         ap_rputs(HTML_HEADER, r);
>         if (gotlock) {
>             /* Increment the counter */
>             base = (exipc_data *)apr_shm_baseaddr_get(exipc_shm);
>             base->counter++;
>             /* Send a page with our pid and the new value of the 
> counter. */
>             ap_rprintf(r, "<p>Lock acquired after camping out for %ld 
> microseoncds.</p>\n",
>                        (long int) timecamped);
>             ap_rputs("<table border=\"1\">\n", r);
>             ap_rprintf(r, "<tr><td>Child pid:</td><td>%d</td></tr>\n",
>                        (int) getpid());
>             ap_rprintf(r, "<tr><td>Counter:</td><td>%u</td></tr>\n",
>                        (unsigned int)base->counter);
>             ap_rputs("</table>\n", r);
>         } else {
>             /*
>              * Send a page saying that we couldn't get the lock. Don't 
> say
>              * what the counter is, because without the lock the value 
> could
>              * race.
>              */
>              ap_rprintf(r, "<p>Child %d failed to acquire lock "
>                         "after camping out for %d microseconds.</p>\n",
>                         (int) getpid(), (int) timecamped);
>         }
>         ap_rputs(HTML_FOOTER, r);
>     } /* r->header_only */
>
>     /* Release the lock */
>     if (gotlock)
>         rs = apr_global_mutex_unlock(exipc_mutex);
>     /* Swallowing the result because what are we going to do with it at
>      * this stage?
>      */
>
>     return OK;
> }
>
> static void exipc_register_hooks(apr_pool_t *p)
> {
>     ap_hook_post_config(exipc_post_config, NULL, NULL, 
> APR_HOOK_MIDDLE);
>     ap_hook_child_init(exipc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
>     ap_hook_handler(exipc_handler, NULL, NULL, APR_HOOK_MIDDLE);
> }
>
> /* Dispatch list for API hooks */
> module AP_MODULE_DECLARE_DATA example_ipc_module = {
>     STANDARD20_MODULE_STUFF,
>     NULL,                  /* create per-dir    config structures */
>     NULL,                  /* merge  per-dir    config structures */
>     NULL,                  /* create per-server config structures */
>     NULL,                  /* merge  per-server config structures */
>     NULL,                  /* table of config file commands       */
>     exipc_register_hooks   /* register hooks                      */
> };
>
>
> -- 
> sander@temme.net              http://www.temme.net/sander/
> PGP FP: 51B4 8727 466A 0BC3 69F4  B7B8 B2BE BC40 1529 24AF