passengerを読み解く(例のあのプロセスが動くまで) vol2

さて,第二回ではApache拡張ライブラリ内部を少し読んでみたいと思う.
その前に,Passengerでの高速化のうんちくを一つ.
 

高速化について

Passengerではmod_rubyのようにruby自身をApacheに取り込んで,プロセス生成せずに高速化を図る手法とは違う.
FastCGIのように一度作ったプロセスは極力永続化させようという手法で高速化を図る.
ここらへんは
Passenger アーキテクチャ概要 (koshigoe 仮訳) 2. Passenger のアーキテクチャ
を見ていただくとよいかと思う.
 
簡単に説明すると,PassengerはApplicationPoolServerというまんまアプリケーションをプールして管理するプロセスを
立ち上げて,Apacheのプロセスはそのプロセスに接続を依頼する.
そして,ApplicationPoolServerはプールしておいたプロセスとApacheを接続すr.
ここまでがC++で記述されている.
 
そして,Ruby側でもSpawnServerという何がなんだか分からない名前のサーバがいて,そいつが,Railsフレームワーク
アプリケーション自体のコードをキャッシュしている.
 
そういうことで,プロセスも起動しなくていいし,Railsを読み込み直す必要もなく,高速に動作するらしい.
(本当か?)
 

ApplicationPoolServerExecutable

pgrep -lf passenger

などとすると,

7596 /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/ext/apache2/ApplicationPoolServerExecutable
 0 /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/bin/passenger-spawn-server  /usr/bin/ruby1.8
  /tmp/passenger_status.7594.fifo

23267 /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/ext/apache2/ApplicationPoolServerExecutable
 0 /usr/lib/ruby/gems/1.8/gems/passenger-2.0.3/bin/passenger-spawn-server  /usr/bin/ruby1.8
  /tmp/passenger_status.7897.fifo

 
こういうプロセスが立ち上がっているかと思う.
これが永続化しているプロセスだ.
 

mod_passenger.c

#include <httpd.h>
#include <http_config.h>
#include "Configuration.h"
#include "Hooks.h"

module AP_MODULE_DECLARE_DATA passenger_module = {
        STANDARD20_MODULE_STUFF,
        passenger_config_create_dir,        /* create per-dir config structs */
        passenger_config_merge_dir,         /* merge per-dir config structs */
        passenger_config_create_server,     /* create per-server config structs */
        passenger_config_merge_server,      /* merge per-server config structs */
        passenger_commands,                 /* table of config file commands */
        passenger_register_hooks,           /* register hooks */
};

これは,apacheモジュールを作る際のお約束である.
詳しくはこちらApacheModuleでWebアプリケーションをつくろう (1/7):CodeZine(コードジン)
config_*系は設定ファイルの値を設定する処理が動く.
 
さて,ここで重要な関数は「passenger_register_hooks」だ.
 

passenger_register_hooks/Hooks.cpp

/**                                                                                                                                                                         
 * Apache hook registration function.                                                                                                                                       
 */
void
passenger_register_hooks(apr_pool_t *p) {
        ap_hook_post_config(init_module, NULL, NULL, APR_HOOK_MIDDLE);
        ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
        ap_hook_map_to_storage(map_to_storage, NULL, NULL, APR_HOOK_FIRST);
        ap_hook_handler(handle_request, NULL, NULL, APR_HOOK_MIDDLE);
}

一つずつ読んでいく.先は長い...
 

init_module/Hooks.cpp

いろいろあるが,やっている事は実質一行で

                hooks = new Hooks(pconf, plog, ptemp, s);

だ.
 

Hooks/Hooks.cpp

public:                                                                                                                                                                     
        Hooks(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) {
  ...
                // new ApplicationPoolServer                                                                                                                                
                applicationPoolServer = ptr(
                        new ApplicationPoolServer(
                                applicationPoolServerExe, spawnServer, "",
                                ruby, user)
                );

ここも実質一行読めばいい.
ApplicationPoolServerをnewしている所である.
引数の値は

  • applicationPoolServerExe = "passenger/ext/apache2/ApplicationPoolServerExecutable"
  • spawnServer = "passenger/bin/passenger-spawn-server"
  • ruby = "/usr/bin/ruby1.8"
  • user = "nobody"

と設定ファイルのパラメータで設定したものである.
設定ファイルのパラメータとその意味はこちらvividfire.net
 

ApplicationPoolServer/ApplicationPoolServer.h

        ApplicationPoolServer(const string &serverExecutable,
                     const string &spawnServerCommand,
                     const string &logFile = "",
                     const string &rubyCommand = "ruby",
                     const string &user = "")
        : m_serverExecutable(serverExecutable),
          m_spawnServerCommand(spawnServerCommand),
          m_logFile(logFile),
          m_rubyCommand(rubyCommand),
          m_user(user) {
                serverSocket = -1;
                serverPid = 0;
                this_thread::disable_syscall_interruption dsi;
                restartServer();
        }

 
ここで気にするのはもうrestartServer()でいい.
 

restartServer()/ApplicationPoolServer.h

        void restartServer() {
  ...
                pid = InterruptableCalls::fork();
                if (pid == 0) { // Child process.
  ...
                        //execute!                                                                                                                                         
                        execlp(
                                #if 0
                                        "valgrind",
                                        "valgrind",
                                #else
                                        m_serverExecutable.c_str(),
                                #endif
                                m_serverExecutable.c_str(),
                                toString(Passenger::getLogLevel()).c_str(),
                                m_spawnServerCommand.c_str(),
                                m_logFile.c_str(),
                                m_rubyCommand.c_str(),
                                m_user.c_str(),
                                statusReportFIFO.c_str(),
                                NULL);
  

お,起動した.
ここで起動したのはpgrepでみたあのプロセスである.
ふむふむ.
 

次回は

passenger_register_hooksの続きを読んでいく.