mod_example_ipc.c 12.4 KB
Newer Older
powelld's avatar
powelld committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 *  mod_example_ipc -- Apache sample module
 *
 * This module illustrates the use in an Apache 2.x module of the Interprocess
 * Communications routines that come with APR. It is example code, and not meant
 * to be used in a production server.
 *
 * To play with this sample module first compile it into a DSO file and install
 * it into Apache's modules directory by running:
 *
 *    $ /path/to/apache2/bin/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 restart Apache via
 *
 *    $ /path/to/apache2/bin/apachectl restart
 *
 * The module allocates a counter in shared memory, which is incremented by the
 * request handler under a mutex. After installation, activate the handler by
 * hitting the URL configured above with ab at various concurrency levels to see
 * how mutex contention affects server performance.
 */

#include "apr.h"
#include "apr_strings.h"

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

#if APR_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif

#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
/* Number of microseconds the handler sits on the lock once acquired. */
#define SLEEPYTIME 1000

apr_shm_t *exipc_shm; /* Pointer to shared memory block */
char *shmfilename; /* Shared memory file name, used on some systems */
apr_global_mutex_t *exipc_mutex; /* Lock around shared memory segment access */
static const char *exipc_mutex_type = "example-ipc-shm";

/* Data structure for shared memory block */
typedef struct exipc_data {
    apr_uint64_t counter;
    /* More fields if necessary */
} exipc_data;

/*
 * Clean up the shared memory block. This function is registered as
 * cleanup function for the configuration pool, which gets called
 * on restarts. It assures that the new children will not talk to a stale
 * shared memory segment.
 */
static apr_status_t shm_cleanup_wrapper(void *unused)
{
    if (exipc_shm)
        return apr_shm_destroy(exipc_shm);
    return OK;
}

/*
 * This routine is called in the parent; we must register our
 * mutex type before the config is processed so that users can
 * adjust the mutex settings using the Mutex directive.
 */

static int exipc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
                            apr_pool_t *ptemp)
{
    ap_mutex_register(pconf, exipc_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
    return OK;
}

/*
 * 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)
{
    apr_status_t rs;
    exipc_data *base;
    const char *tempdir;


    /*
     * Do nothing if we are not creating the final configuration.
     * 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.
     */
    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
        return OK;

    /*
     * The shared memory allocation routines take a file name.
     * Depending on system-specific implementation of these
     * routines, that file may or may not actually be created. We'd
     * like to store those files in the operating system's designated
     * temporary directory, which APR can point us to.
     */
    rs = apr_temp_dir_get(&tempdir, pconf);
    if (APR_SUCCESS != rs) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, APLOGNO(02992)
                     "Failed to find temporary directory");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* 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.
     */
    shmfilename = apr_psprintf(pconf, "%s/httpd_shm.%ld", tempdir,
                               (long int)getpid());

    /* Now create that segment */
    rs = apr_shm_create(&exipc_shm, sizeof(exipc_data),
                        (const char *) shmfilename, pconf);
    if (APR_SUCCESS != rs) {
        ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, APLOGNO(02993)
                     "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 */

    rs = ap_global_mutex_create(&exipc_mutex, NULL, exipc_mutex_type, NULL,
                                s, pconf, 0);
    if (APR_SUCCESS != rs) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
     * Destroy the shm segment when the configuration pool gets destroyed. This
     * happens on server restarts. The parent will then (above) allocate a new
     * shm segment that the new children will bind to.
     */
    apr_pool_cleanup_register(pconf, NULL, shm_cleanup_wrapper,
                              apr_pool_cleanup_null);
    return OK;
}

/*
 * 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;

    /*
     * Re-open the mutex for the child. Note we're reusing
     * the mutex pointer global here.
     */
    rs = apr_global_mutex_child_init(&exipc_mutex,
                                     apr_global_mutex_lockfile(exipc_mutex),
                                     p);
    if (APR_SUCCESS != rs) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rs, s, APLOGNO(02994)
                     "Failed to reopen mutex %s in child",
                     exipc_mutex_type);
        /* There's really nothing else we can do here, since This
         * routine doesn't return a status. If this ever goes wrong,
         * it will turn Apache into a fork bomb. Let's hope it never
         * will.
         */
        exit(1); /* Ugly, but what else? */
    }
}

/* 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. This code is a lot more involved than
     * it usually needs to be, because the process based trylock
     * routine is not implemented on unix platforms. I left it in to
     * show how it would work if trylock worked, and for situations
     * and platforms where trylock works.
     */
    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_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_SUCCESS == rs) {
                gotlock = 1;
                break; /* Out of the loop */
            }
            else {
                /* Some error, log and bail */
                ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server, APLOGNO(02995)
                             "Child %ld failed to acquire lock",
                             (long int)getpid());
                break; /* Out of the loop without having the lock */
            }
        }
        else {
            /* Some other error, log and bail */
            ap_log_error(APLOG_MARK, APLOG_ERR, rs, r->server, APLOGNO(02996)
                         "Child %ld failed to try and acquire lock",
                         (long int)getpid());
            break; /* Out of the loop without having the lock */
        }

        /*
         * The only way to get to this point is if the trylock worked
         * and returned BUSY. So, bump the time and try again
         */
        timecamped += CAMPOUT;
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server, APLOGNO(03187)
                     "Child %ld camping out on mutex for %" APR_INT64_T_FMT
                     " microseconds",
                     (long int) getpid(), timecamped);
    } /* Lock acquisition loop */

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

    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 %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_pre_config(exipc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    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 */
AP_DECLARE_MODULE(example_ipc) = {
    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                      */
};