2014年7月15日火曜日

リクエストの処理の流れ

handlerフック関数の実行の続きで、ap_process_async_request()関数から呼び出し関係を遡ってみる。


(1) ap_process_http_async_connection関数

呼び出し元の関数ではリクエストを読込み(ap_read_request())、ap_process_async_request()処理を実行している。

(httpd-2.4.9/modules/http/http_core.c)

    124 static int ap_process_http_async_connection(conn_rec *c)
    125 {
    126     request_rec *r;
    127     conn_state_t *cs = c->cs;
  :
    132     while (cs->state == CONN_STATE_READ_REQUEST_LINE) {
    133         ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
    134
    135         if ((r = ap_read_request(c))) {
リクエスト行とリクエストヘッダを読み込み、request_rec情報に格納する。
ここで直接呼び出されるフック関数の実行関数には次のものがある
 * ap_run_create_request
 * ap_run_pre_read_request
 * ap_run_post_read_request
 * ap_run_log_transaction(エラー発生の場合)

     :
    141             if (r->status == HTTP_OK) {
    142                 cs->state = CONN_STATE_HANDLER;
    143                 ap_process_async_request(r);
     :
    151                 r = NULL;
    152             }
    153
    154             if (cs->state != CONN_STATE_WRITE_COMPLETION &&
    155                 cs->state != CONN_STATE_SUSPENDED) {
    156                 /* Something went wrong; close the connection */
    157                 cs->state = CONN_STATE_LINGER;
    158             }
    159         }
    160         else {   /* ap_read_request failed - client may have closed */
    161             cs->state = CONN_STATE_LINGER;
    162         }
    163     }
    164
    165     return OK;
    166 }

ここで conn_state_t という構造体が出てくる。
これは、コネクションの状態を管理している。

(httpd-2.4.9/include/httpd.h)

   1187 struct conn_state_t {
   1188     /** Current state of the connection */
   1189     conn_state_e state;
   1190     /** Whether to read instead of write, or write instead of read */
   1191     conn_sense_e sense;
   1192 };

ここにあるconn_state_e(列挙型)変数 stateは次の通り

(httpd-2.4.9/include/httpd.h)

   1167 typedef enum  {
   1168     CONN_STATE_CHECK_REQUEST_LINE_READABLE,
   1169     CONN_STATE_READ_REQUEST_LINE,
   1170     CONN_STATE_HANDLER,
   1171     CONN_STATE_WRITE_COMPLETION,
   1172     CONN_STATE_SUSPENDED,
   1173     CONN_STATE_LINGER,          /* connection may be closed with lingering */
   1174     CONN_STATE_LINGER_NORMAL,   /* MPM has started lingering close with normal timeout */
   1175     CONN_STATE_LINGER_SHORT     /* MPM has started lingering close with short timeout */
   1176 } conn_state_e;

ap_process_http_async_connection()でチェックされている CONN_STATE_READ_REQUEST_LINE は、その名の通り、リクエストの読み込みということだ。
リクエストの読み込み後、CONN_STATE_HANDLERに更新される(142行目)。
そして、ap_process_async_request()が実行される。

その処理を終えると、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDかチェックされ、いずれでもなければ、CONN_STATE_LINGERで更新される(157行目)。

この時点で、cs->stateは、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDか、CONN_STATE_LINGERということになるので、132行目からのwhile (cs->state == CONN_STATE_READ_REQUEST_LINE)ループからは抜ける。

この処理だけに着目する限り、whileループを実査にループする経路はなさそうだ。

(2)process_connectionフック関数

さて、もう一段、呼び出し元を辿ると、次の関数に辿り着く。

(httpd-2.4.9/modules/http/http_core.c)

    225 static int ap_process_http_connection(conn_rec *c)
    226 {
    227     if (async_mpm && !c->clogging_input_filters) {
    228         return ap_process_http_async_connection(c);
    229     }
    230     else {
    231         return ap_process_http_sync_connection(c);
    232     }
    233 }

これはフック関数だ。
以下で登録されている。process_connectionフック関数である。

(httpd-2.4.9/modules/http/http_core.c)

    275     ap_hook_process_connection(ap_process_http_connection, NULL, NULL,
    276                                APR_HOOK_REALLY_LAST);

上記のap_process_http_connection()で使用されているフラグasync_mpmはap_mpm_query()関数で設定されている。

    263     if (ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm) != APR_SUCCESS) {
    264         async_mpm = 0;
    265     }

このMPMの情報を取得する関数自体が内部でmpm_queryフック関数を呼び出している。
MPMによって、登録されているフック関数が異なり、実際にセットされる値も変わるという訳だ。
event MPMの場合は、このmpm_queryフック関数は、event_query()だ。
この関数が、AP_MPMQ_IS_ASYNCというコードを与えられたときに、引数async_mpmにセットする値は、1である。

ちなみに、worker MPMにおける、mpm_queryフック関数はworker_query()だが、AP_MPMQ_IS_ASYNCに対して返す値の指定がない。ここでは、APR_ENOTIMPLがエラーとして返されるので、結果として、264行目によって値が0となる。
prefork MPMも worker MPMと同様だった。

clogging_input_filtersは、2.4.9では使用されていないので、初期値の0のままだ。

従って、ap_process_http_connection()関数は、event MPMでは、ap_process_http_async_connection()が実行され、worker MPM/prefork MPMでは、ap_process_http_sync_connection()が実行される。

clogging_input_filtersをセットすると、worker MPMと同様にリクエスト処理でワーカスレッドを占有する、というのは、要はこの処理の分岐の話だ。


(3)event MPM: process_socket関数


続いて、このap_process_http_connection()を実行するprocess_connectionフック関数の呼び出し箇所を見てみる。
event MPMでは process_socket()関数の処理で呼ばれている。
これは、event MPMのworkerスレッドで実行される。
listenerスレッドが受け付けたリクエストの処理を行うのが、workerスレッドのprocess_socket()関数だ。

もう少し細かく見ると、listenerスレッドは、listenソケットのacceptを監視し、既に接続済みのソケットが読込可能/書込可能となるのを監視している。
またapr_skiplistを利用したタイマー処理のタイムアップも監視しており、そのほかにも4種類のタイムアウト処理を実行する。

読込可能チェックには、以下の状態がある。
  • KeepAlive接続における次リクエストの受信待ち
  • lingeringクローズでの通信相手ソケットからのクローズ(FIN)待ち

書込可能チェックは、以下の状態だ。
  • クライアントに送ろうとしたデータがブロックされてしまった場合(送信できなかった場合)、再び、書込可能なるのを待っている状態
これらのうち、lingeringクローズソケットのクローズ処理以外は、workerスレッドで処理される。
そして、acceptされたソケットの処理、KeepAlive接続の読込処理、書込み可能になったソケットの書込処理がprocess_socket関数で行われる。
タイマー処理は、process_socket関数では処理されない。

新たなソケットの処理とKeepAlive接続の読込処理では、conn_state_tがCONN_STATE_CHECK_REQUEST_LINE_READABLE状態になる。
書込み可能となったソケットの場合は、conn_state_tがCONN_STATE_WRITE_COMPLETION状態だ。

ここで、process_socket関数でap_run_process_connection()を実行するのは、CONN_STATE_CHECK_REQUEST_LINE_READABLE状態の場合の処理となる。

    881 static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock,
    882                           event_conn_state_t * cs, int my_child_num,
    883                           int my_thread_num)
    884 {
    :

    893     if (cs == NULL) {           /* This is a new connection */

新しくacceptされたソケットの処理の場合、csはNULLとなっている。
KeepAlive接続の処理の場合はcsは作成済みなので、ここの処理を経由しない。
ここでコネクション情報の初期設定やevent_conn_state_t情報の初期設定
などの初期処理が実行される。
    897         c = ap_run_create_connection(p, ap_server_conf, sock,
    898                                      conn_id, sbh, cs->bucket_alloc);
     :
    920         ap_update_vhost_given_ip(c);
    921
    922         rc = ap_run_pre_connection(c, sock);


    942         cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
    943
    944         cs->pub.sense = CONN_SENSE_DEFAULT;
    945     }
     :
    952     if (c->clogging_input_filters && !c->aborted) {

clogging_input_filtersフラグが立っている場合の処理。
実行されるのはap_run_process_connection()で、フラグが立っていない場合と
同じだが、終了時のconn_state_tに設定される状態が、CONN_STATE_LINGER状態
に限定されている(SUSPENDでなければ)。
     :
    959         apr_atomic_inc32(&clogged_count);
    960         ap_run_process_connection(c);
    961         if (cs->pub.state != CONN_STATE_SUSPENDED) {
    962             cs->pub.state = CONN_STATE_LINGER;
    963         }
    964         apr_atomic_dec32(&clogged_count);
    965     }
     :


    967 read_request:
    968     if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE) {

1件のリクエスト処理はここから始まる。
    969         if (!c->aborted) {
    970             ap_run_process_connection(c);

これこそ、今まで見てきた処理を実行する process_connectionフック関数の
実行だ。
     :
    976         }
    977         else {
    978             cs->pub.state = CONN_STATE_LINGER;
    979         }
    980     }
    981
    982     if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) {

process_connectionフック関数からreturnすると、通常、conn_state_tは
CONN_STATE_WRITE_COMPLETION状態となっている。
ap_process_async_request()関数の最後に実行される 1件のリクエスト処理の
終了処理であるap_process_request_after_handler関数内で、
CONN_STATE_WRITE_COMPLETIONがセットされる。
そして、ここでは bucket brigadeが NULLで出力フィルタに引き渡されている。
内部に保持されているbucket brigadeが処理対象ということだ。
    983         ap_filter_t *output_filter = c->output_filters;
     :

    989         rv = output_filter->frec->filter_func.out_func(output_filter, NULL);

     :
   1065     return;
   1066 }


この処理は、listenerスレッドと相互に関係しながら処理を進めるので、少し分かりにくい気がする。
が、リクエスト処理の流れを追うという意味では、ここが最上流、源流にあたる。
そういうわけで、上流から下流に簡単な図を書いてみた。


フック関数を中心に追ってみた。今日は終わり。

0 件のコメント:

コメントを投稿