Tips for Developing Apache 2.0.x modules

[ はじめに | 排他処理 | 共有メモリ | リクエストの処理 | C++ | デバッグ | 参考文献 ]

はじめに

このページでは,私が mod_uploader の開発の過程で得た Tips について紹介しています.

排他処理

他のプロセス及びスレッドに対して排他処理を行うには apr_global_lock_t が,他のスレッドに対して排他処理を行うには apr_thread_mutex_t が利用できます.

apr_global_mutex_t の使い方

基本的には,次の手順で使用します.

  1. post_config ステージ
  2. child_init ステージ

unixd_set_global_mutex_perms を実行すべきか どうかは AP_NEED_SET_MUTEX_PERMS が define されているかどうかで判断すれば良いのですが,このマクロは Apache 2.0 系には用意されていません.Apache 2.0 系のモジュールを作成する場合は 次のようにしてモジュール側で define してやる必要があります. (AP_SERVER_MAJORVERSION_NUMBER が Apache 2.1 系のみに存在することを 利用しています)

/* Apache 2.0 系の場合は,AP_NEED_SET_MUTEX_PERMS の define を試みる. */
#ifndef AP_SERVER_MAJORVERSION_NUMBER
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#define AP_NEED_SET_MUTEX_PERMS
#endif
#endif

以上をふまえると,モジュールのコードは次のようになります.

#ifndef AP_SERVER_MAJORVERSION_NUMBER
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#define AP_NEED_SET_MUTEX_PERMS
#endif
#endif

#include "apr_global_mutex.h"
#ifdef AP_NEED_SET_MUTEX_PERMS
#include "unixd.h"
#endif

typedef struct ServerConfig {
    char *mutex_path;
    apr_global_mutex_t *mutex;
} sconfig;

static void *create_server_config(apr_pool_t *p, server_rec *s)
{
    sconfig *config;

    config = (sconfig *)(apr_palloc(p, sizeof(sconfig)));

    /* ここで指定されるファイルは,必要に応じて自動的に生成されます. */
    /* あらかじめ作成しておく必要はありません. */
    config->mutex_path = "/path/to/global/mutex/file";

    return config;
}

static int post_config(apr_pool_t *pconf, apr_pool_t *plog,
                       apr_pool_t *ptemp, server_rec *s)
{
    sconfig *config;
    void *user_data;
    apr_status_t status;

    /* post_config は Apache の起動時に二度呼ばれるので,ダミーの */
    /* user_data を使って一度目では apr_global_mutex_t を生成しない */
    /* ようにする. */
    apr_pool_userdata_get(&user_data, USER_DATA_KEY, s->process->pool);
    if (user_data == NULL) {
        apr_pool_userdata_set((const void *)(1), USER_DATA_KEY,
                              apr_pool_cleanup_null, s->process->pool);
        return OK;
    }

    config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));

    status = apr_global_mutex_create(&(config->glock), config->glock_path,
                                     APR_LOCK_DEFAULT, pconf);
    if (status != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

#ifdef AP_NEED_SET_MUTEX_PERMS
    status = unixd_set_global_mutex_perms(config->glock);
    if (status != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }
#endif

    return OK;
}

static void child_init(apr_pool_t *p, server_rec *s)
{
    sconfig *config;

    config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));

    if (apr_global_mutex_child_init(&(config->glock),
                                    config->glock_path, p) != APR_SUCCESS) {
        SERROR("Can not attach to global mutex (%s).", config->glock_path);

        return;
    }
}

static void register_hooks(apr_pool_t *pool)
{
    ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
    ap_hook_child_init(child_init, NULL, NULL, APR_HOOK_REALLY_FIRST);
    /* (略)*/
}

module AP_MODULE_DECLARE_DATA uploader_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    create_server_config,
    NULL,
    NULL,
    register_hooks
};

共有メモリ

他のプロセスとメモリの共有を行うには apr_shm_t を利用します.実際に 動くコンパクトなサンプルとしては, mod_shm_counter がお薦めです.

apr_shm_t の使い方

基本的には,次の手順で使用します.

  1. post_config ステージ
  2. child_init ステージ

モジュールのコードは次のようになります.

#include "apr_shm.h"
typedef struct ServerConfig {
    char *shm_path;
    /* 共有メモリ */
    apr_shm_t *shm_data;
    /* プロセスが実際に読み書きするメモリ */
    char *data;
} sconfig;

static void *create_server_config(apr_pool_t *p, server_rec *s)
{
    sconfig *config;

    config = (sconfig *)(apr_palloc(p, sizeof(sconfig)));

    /* ここで指定されるファイルは,必要に応じて自動的に生成されます. */
    /* あらかじめ作成しておく必要はありません. */
    config->shm_path = "/path/to/shm/file";
    config->data_shm = NULL;

    return config;
}

static int post_config(apr_pool_t *pconf, apr_pool_t *plog,
                       apr_pool_t *ptemp, server_rec *s)
{
    sconfig *config;
    void *user_data;
    apr_status_t status;

    /* post_config は Apache の起動時に二度呼ばれるので,ダミーの */
    /* user_data を使って一度目では apr_global_mutex_t を生成しない */
    /* ようにする. */
    apr_pool_userdata_get(&user_data, USER_DATA_KEY, s->process->pool);
    if (user_data == NULL) {
        apr_pool_userdata_set((const void *)(1), USER_DATA_KEY,
                              apr_pool_cleanup_null, s->process->pool);
        return OK;
    }

    config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));

    /* 1024 バイトの共有メモリを生成 */
    status = apr_shm_create(&(config->shm_data), sizeof(char)*1024,
                            config->shm_path, pconf);
    if (status != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    config->data = (char *)(apr_shm_baseaddr_get(config->shm_data);

    /* 必要に応じて初期化 */
    memset(config->shm_data, 0, sizeof(char)*1024);

    return OK;
}

static void child_init(apr_pool_t *p, server_rec *s)
{
    sconfig *config;

    config = (sconfig *)(ap_get_module_config(s->module_config, &sample_module));

    /* この条件が成り立つときのみ attach する. */
    if (!config->data_shm) {
        if (apr_shm_attach(&(config->data_shm), config->shm_path,
                           p) != APR_SUCCESS) {
            return;
        }
    }

    config->data = (char *)(apr_shm_baseaddr_get(config->data_shm));
}

リクエストの処理

Apache 2.x で POST されたデータを読み込むには次の二つの方法があります.

原因は調べ切れていないのですが,後者の方法を使った場合,POST された データのサイズに比例した(大体 1/40)のメモリをApache 内部で消費してし まうようです.巨大なデータを扱うモジュールを作成する場合は注意してく ださい.

C++

Apche のモジュールを C++ で開発する場合,いくつか注意点があります.基本的な物については, Apache API C++ Cookbook を参照してください.Apache 1.x 向けに書かれた物ですが, ほとんどの項目はそのまま Apache 2.x にも使えます. 以下では,その他の注意点について説明します.

APR_INT64_C

C++ でモジュールを書いていて,「INT64_C が未定義です.」みたいなエラー が出た場合は,__STDC_CONSTANT_MACROS を define してやりましょう.

参考: /usr/include/stdint.h

/* The ISO C99 standard specifies that in C++ implementations these
   should only be defined if explicitly requested.  */
#if !defined __cplusplus || defined __STDC_CONSTANT_MACROS
... (INT64_C の define)
#endif

デバッグ

Apache のモジュールを開発する際の便利なデバッグ方法を紹介します.

Apache の Debug ビルド

モジュールを開発する場合,APR Pool のデバッグ機能を有効にした Apache を用いることで,メモリ関連のバグを早い段階で見つけることがで きます.Unix の場合は次のようにすることで Debug ビルドが行えます. (参考: APR_POOL_DEBUG is functioning again WAS: RE: Don't use APR_POOL_DEBUG )

$ CPPFLAGS=-DAPR_POOL_DEBUG ./configure ...
$ make

WIndows の場合は,次のようにします. (参考: Compiling Apache for Microsoft Windows )

> vsvar32.bat
> nmake /f Makefile.win _apached

UNIX でのデバッグ

メモリ関連のバグの場合,デバッグオプション(-g) をつけてコンパイルし, Valgrind 上で Apache を実行するのが手軽です.Apache にデバッグモード (-X) を指定するのがポイントです.

$ valgrind -v --leak-check=yes --show-reachable=yes --tool=memcheck /usr/sbin/apache2 -X -f /path/to/httpd.conf 2>&1 | tee valgrind.log

Apache をデバッグモードで実行した場合,標準出力への出力がコンソール に表示されるので,いつもの(?) printf デバッグも行えます.

WIndows でのデバッグ

普通の Windows アプリケーションの場合と同様,デバッグオプション(/Od /MDd /GS /RTCsu /Zi) をつけてコンパイルして,VisualStudio を使うのが お薦めです.

参考文献

Apacheモジュール プログラミングガイド モジュールの開発に役立つ文献を紹介します.

Apacheモジュール プログラミングガイド
Apache のモジュール作成に必要になる事柄を一通り説明した本です.痒い ところに手が届いているので,手元に置いておくと重宝します.
Advanced Topics in Module Design: Threadsafety and Portability
Apache2 でモジュールを作成するときに必要になってくるテクニックが解 説されたパワポです.そんなに長くないので,モジュール書く前にさらっと読 んでおきましょう.
Apache API C++ Cookbook
C++ を使って Apache のモジュールを作成する際の注意事項について説明 したサイトです.