ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

mod_mruby を読む。その2

前回です。

udzura.hatenablog.jp

前回読んでいなかった重要なメソッドに ap_mrb_set_mrb_state があった。

定義:

static void ap_mrb_set_mrb_state(apr_pool_t *pool, mrb_state *mrb)
{
  TRACER;
  apr_pool_userdata_set(mrb, "mod_mruby_state", cleanup_mrb_state, pool);
  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "%s DEBUG %s: set mrb_state to mod_mruby_state", MODULE_NAME,
               __func__);
}

apr_pool_userdata_set というのが apr_pool_t に、特定のキーを指定して任意のデータを紐づける関数。

そうするとpconfというのが鍵だが、まずserver_recってのがここから確認できて

メンバのprocessっていうのが*process_recで

実は二つプールがあって使い分けできて

apr_pool_t* process_rec::pconf
    Configuration pool. Cleared upon restart

apr_pool_t* process_rec::pool
    Global pool. Cleared upon normal exit

とのこと。restartとnormal exitってなにがちがうんだ...

pconfはワーカが入れ替わったら消えるけど、poolはワーカの根っこに紐づくから全体の停止がない限り消えない?

ひとまず ap_mrb_set_mrb_state はわかった。 ap_mrb_get_mrb_state も同じようなコードだと想像できる。


ようやく mod_mruby_cmds を読むと、

static const command_rec mod_mruby_cmds[] = {

    AP_INIT_FLAG("mrubyHandlerEnable", set_mod_mruby_handler_enable, NULL, RSRC_CONF | ACCESS_CONF,
                 "can use addhandler or sethandler for mruby-script. (default Off)"),
    AP_INIT_TAKE1("mrubyHandlerCode", set_mod_mruby_handler_inline, NULL, RSRC_CONF | ACCESS_CONF,
                  "hook inline code for handler phase."),
    MOD_MRUBY_SET_ALL_CMDS_INLINE(handler, Handler),
    MOD_MRUBY_SET_ALL_CMDS_INLINE(post_read_request, PostReadRequest),
//...

    AP_INIT_TAKE12("mrubyHandler", set_mod_mruby_handler, NULL, RSRC_CONF | ACCESS_CONF, "hook for handler phase."),

    MOD_MRUBY_SET_ALL_CMDS(handler, Handler),
    MOD_MRUBY_SET_ALL_CMDS(post_config, PostConfig),
    MOD_MRUBY_SET_ALL_CMDS(child_init, ChildInit),
//...
    AP_INIT_TAKE12("mrubyOutputFilter", set_mod_mruby_output_filter, NULL, RSRC_CONF | ACCESS_CONF,
                   "set mruby output filter script."),

    {NULL}};

command_rec 型の配列(これは struct command__structエイリアス)であるとわかる。見た目の通り明らかにディレクティブを定義している。

command_rec 型のレコードを作るためにいくつかマクロがあるとわかる。

まず AP_INIT_* のほうはApacheで提供しているマクロで、

ここに一通り載っている。つかっているものは:

  • AP_INIT_FLAG mechanism for declaring a directive which takes a flag (on/off) argument
  • AP_INIT_TAKE1 mechanism for declaring a directive which takes 1 argument
  • AP_INIT_TAKE12 mechanism for declaring a directive which takes 1 or 2 arguments

ここでmod_mrubyを触ったことがある方なら、TAKE1の方はインライン、TAKE12のほうはファイル指定で、optionalな2つ目のフラグをキャッシュの有効無効に使っている、というルールが読み取れると思う。

AP_INIT_TAKE1の引数の意味と取りうる型について説明する。

  • "mrubyHandlerCode" これはディレクティブの名前。
  • set_mod_mruby_handler_inline ディレクティブがセットされた時に動く関数。
    • この関数はマクロでまとめて定義されているので注意なのだが
    • mod_mruby.c#L271-L283
    • static const char *set_mod_mruby_##hook##_inline(cmd_parms * cmd, void *mconfig, const char *arg)
      • らしい、 char* を返すの?(実装ではNULL返してる)。
        • char* はエラーメッセージを返すのか
      • mconfigは下を参照。
      • cmd_parms * からserver_rec、module_configが辿れる
      • mruby_config_t にそれぞれのインラインフックのコードが格納されているので、そこに格納しつつ、
      • ap_mrb_set_stringはmod_mruby_code_t *をアロケートして生成している
      • mod_mruby_compile_code はコンパイルだけをして、mod_mruby_code_tにコンパイルしたバイトコード(コンテクスト)を渡している(c->proc)。
        const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);                                                         \
        mruby_config_t *conf = (mruby_config_t *)ap_get_module_config(cmd->server->module_config, &mruby_module);          \
        if (err != NULL)                                                                                                   \
          return err;                                                                                                      \
        conf->mod_mruby_##hook##_inline_code = ap_mrb_set_string(cmd->pool, arg);                                          \
        mod_mruby_compile_code(ap_mrb_get_mrb_state(cmd->server->process->pconf), conf->mod_mruby_##hook##_inline_code,    \
                               cmd->server);                                                                               \
        return NULL;
  • NULL 三番目の引数 mconfig は使っていない。これは複数回呼び出すようなディレクティブで状態を持ち回す時に使うそう
  • RSRC_CONF | ACCESS_CONF
  • "hook inline code for handler phase." これはヘルプメッセージ。

なおインラインじゃない方の定義:

#define SET_MOD_MRUBY_DIR_CMDS(hook)                                                                                   \
  static const char *set_mod_mruby_##hook(cmd_parms *cmd, void *mconfig, const char *path, const char *cache_opt);     \
  static const char *set_mod_mruby_##hook(cmd_parms *cmd, void *mconfig, const char *path, const char *cache_opt)      \
  {                                                                                                                    \
    const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT);                                                         \
    mruby_dir_config_t *dir_conf = (mruby_dir_config_t *)mconfig;                                                      \
    TRACER;                                                                                                            \
    if (err != NULL)                                                                                                   \
      return err;                                                                                                      \
    dir_conf->mod_mruby_##hook##_code = ap_mrb_set_file(cmd->pool, path, cache_opt, "" #hook "");                      \
    mod_mruby_compile_code(ap_mrb_get_mrb_state(cmd->server->process->pconf), dir_conf->mod_mruby_##hook##_code,       \
                           cmd->server);                                                                               \
    return NULL;                                                                                                       \
  }

は、ap_mrb_set_file が大きな違いで、mod_mruby_code_tのCacheフラグを一緒に操作している。 TAKE12 だと関数の側の引数の数が増えているのも注目する。

で、もう一つのマクロ MOD_MRUBY_SET_ALL_CMDS(_INLINE) は以下の定義:

#define MOD_MRUBY_SET_ALL_CMDS_INLINE(hook, dir_name)                                                                  \
  AP_INIT_TAKE1("mruby" #dir_name "FirstCode", set_mod_mruby_##hook##_first_inline, NULL, RSRC_CONF | ACCESS_CONF,     \
                "hook inline code for " #hook " first phase."),                                                        \
      AP_INIT_TAKE1("mruby" #dir_name "MiddleCode", set_mod_mruby_##hook##_middle_inline, NULL,                        \
                    RSRC_CONF | ACCESS_CONF, "hook inline code for " #hook " middle phase."),                          \
      AP_INIT_TAKE1("mruby" #dir_name "LastCode", set_mod_mruby_##hook##_last_inline, NULL, RSRC_CONF | ACCESS_CONF,   \
                    "hook inline code for " #hook " last phase.")

フックポイントによりFirst, Middle, Lastそれぞれでフックがあるので3つまとめて定義しているということ。

これにより

  • Apacheに存在するほぼすべてのHookでmrubyが動く
  • インラインとファイルの両方を使える、ファイルはバイトコードをキャッシュするオプションを持たせる

を実現している。


結構長くなったw

いまだに register_hooks に到達しておらず、登録できないので次はそこへ。