(3)event MPM: process_socket関数 (再)
process_socket()関数は、workerスレッドが実行するリクエスト処理の起点だ。
リクエストの処理の流れ で見ている。
(httpd-2.4.9/server/mpm/event/event.c) 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)
第1引数のapr_thread_t構造体は、pthread_tをメンバに持つスレッド情報。
第2引数のapr_pool_t構造体は、このトランザクションで使用するメモリプール情報。transactionメモリプールである。
第3引数のapr_socket_t構造体は、通信用のソケット情報。
第4引数のevent_conn_state_t構造体は、eventMPMの状態管理情報。
第5引数のint型は、子プロセスの管理番号。スコアボードのプロセス情報のインデックスになる。
第6引数のint型は、ワーカスレッドの管理番号。スコアボードのワーカ情報のインデックスになる。
第2引数のメモリプール、第3引数のソケット、第4引数の接続状態は、listenerスレッドから通信用のキュー(worker_queue)を経由して受け取る。
新規の接続の場合、第4引数の接続状態はNULLが渡ってくるので、新規に作成することになる。
884 {
885 conn_rec *c;
886 long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num);
このマクロは次の計算を行う:my_child_num * thread_limit + my_child_num
thread_limitはThreadLimitディレクティブで指定された値。
887 int rc; 888 ap_sb_handle_t *sbh; 889 890 /* XXX: This will cause unbounded mem usage for long lasting connections */ 891 ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num);
ap_sb_handle_t *sbhは子プロセスのインデックスとワーカのインデックスを保持している。
この関数で領域を確保し、引数のmy_child_numとmy_thread_numが、sbhにセットされる。
この情報は、ap_update_child_status()等の関数の引数に使用され、スコアボードの更新対象のインデックスを指定する。
スコアボードの説明も書いていないようだが、
簡単に書くと、statusモジュールなどでも参照できるプロセス/ワーカスレッドの実行状態などを管理している共有メモリ上の表だ。
これはプロセスのメンテナンス(余剰プロセスの終了や不足分プロセスの生成の処理)でも参照される。
892 893 if (cs == NULL) { /* This is a new connection */
ここは新規接続の場合に行われる分岐となる。
apr_pcalloc()とapr_palloc()の違いは、
apr_pcalloc()ではapr_palloc()を行った後で確保した領域を0x0で初期化している。
create_connectionフック関数を実行する。
戻り値のcはconn_rec情報。
cs->bucket_allocは、生成されるconn_recのbucket_alloc情報にセットされる(core_create_conn()関数)。
bucket_allocはbucket brigadeの生成に使用するメモリの確保や再利用のために使用される。
利用済みのメモリなどは再利用可能なメモリ領域としてこのbucket_allocが管理する。
core_create_conn()はcoreモジュールが提供するcreate_connectionのフック関数だが、
httpdのソースに同梱されている中では、create_connectionフック関数はこのcore_create_conn()しか見当たらない。
(mod_example_hooks.cにも定義はあるが、有効な処理ではない)
core_create_conn()では、conn_rec情報の初期値がいくつかセットされる。
まず、領域がapr_pcalloc()で確保されるので、領域全体は0x0で初期化される。
pool変数には第一引数のp(transactionメモリプール)がセットされる。
server_rec *base_server変数には第2引数のap_server_conf(メインサーバのserver_rec)がセットされる。
apr_sockaddr_t* 型のlocal_addrとclient_addrの各変数は第3引数の、sock(apr_socket_t情報)から取得し、セットされる。
char * 型変数の client_ipとlocal_ipも同様。
long idには、第4引数のconn_idがセットされる。
void *sbhには、第5引数のsbhがセットされる。
以下の 899行目からのif文は、create_connectionフック関数が失敗した場合の処理になる。
確保したbucket_allocを破棄し、transactionメモリプールをクリアする(clearの場合は、中身を消すだけで、プールそのものは残っている)。
そして、transactionメモリプールをworker_queue_infoに返している。
event MPMの子プロセスはworker_queue_infoでアイドル状態のワーカ数や再利用可能なtransactionメモリプールを管理している。
上記のcore_create_conn()の返り値を見ると、apr_socket_addr_get()が失敗してソケットを閉じた場合にこの経路に入っている(return NULL)。
ざっと眺めると、これはgetsockname()が失敗した場合に相当しそうだ。
ちゃんとしたソケットがlistenerスレッドから渡ってくるはずなので、メモリ不足などでも起きない限りこの経路には入りそうではない。
以下は、connection_count変数の加算を行い減算の準備をしている。
減算は、メモリプール(c->pool)を破棄するときに実行されるcleanup関数に登録している(906行目)。
connection_countはこのevent.cファイルのファイルスコープで定義された32ビット符号なし整数。
この子プロセスの持つ接続数が保持されることになる。
conn_rec情報と、event_conn_state_t情報に初期設定を行われる。
conn_rec構造体のcs変数には、cs->pubがセットされている。
ソケットの接続情報(ローカルIPアドレス、ポート番号)をもとに、
対応するIPベースのVirtualHostの設定を確認し、あれば、その情報をconn_recのbase_server情報にセットする。
(初期設定はメインサーバになっている)
pre_connectionフック関数を実行する。
pre_connectionフック関数はcoreモジュール以外からもフック関数が登録される。
最低限coreモジュールのフック関数だけ登録されていればHTTPリクエストは処理できる。
第1引数はconn_rec情報、第2引数はapr_socket_t情報が与えられている。
ソケットに対するTCP_NODELAYオプションを有効にする。
これはデフォルトで有効なっているNagle(ネーグル)アルゴリズムを無効にする。
Nagleアルゴリズムは、細かい点はさておいて、データの送信時にあまり小さなデータをそのまま送信するのではなく、ある程度まとめて送信するために
少し待つ。この結果、小さいデータを細切れのパケットで送るのではなく、大きめのデータをひとつのパケットで送ることができる。
これにはネットワーク帯域の利用効率を高める効果がある。
これを無効にするということは、小さなデータであっても直ちに送信する、ということになる。
コメントにある、持続的(Keep-Alive)接続とNagleアルゴリズムの悪い相互作用というのは、おそらく、Apache httpdがレスポンスを返すとき、
何度か送信する最後のレスポンスの断片の送信処理で、十分なサイズのデータが残っていなければ、Nagleアルゴリズムによって送信が遅延し、
つまり、リクエスト/レスポンスのやり取りの完了が遅延してしまう可能性があるということではないか。
(ソケットを閉じるなら、未送信の小さなデータがあっても送信される)
エラーの場合は、DEBUGログを出力するだけ。
タイムアウトを設定する。
ここでは、apr_socket_t構造体(*csd)のtimeout変数に c->base_server->timeoutをセットする。
core_net_rec構造体 *net に値をセットする。
csdをconn_recのconn_configのcoreモジュール情報領域に登録する。
入力フィルタ ap_core_input_filter_handle("CORE_IN")を登録する。
実体は ap_core_input_filter()関数だ。
net情報がフィルタ処理のコンテキスト情報として渡されている。
出力フィルタ ap_core_output_filter_handle("CORE")を登録する。
実体は ap_core_output_filter()関数だ。
こちらでも、net情報がフィルタ処理のコンテキスト情報として渡されている。
mod_sslのpre_connectionフック関数ではHTTPSリクエスト処理のために必要なフィルタが追加されるが、これは改めて確認して書くことにする。
以下の923行目からのif文は、pre_connectionフック関数が失敗した場合の処理だ。
ただし、ここでは、conn_recのabortedフラグに1をセットするだけで、関数処理が終了していない。
create_connectionフック関数の失敗時とは異なっている。
942行目で接続状態の初期値がセットされる(接続を受け付けた状態)。
cs->pubの値は、作成したconn_rec情報のcs変数で参照できる。
ここまでで、conn_rec情報のセットアップが完了する。
こちらはKeepAlive接続の場合や、書込み可能が検知された場合の条件(この接続がworkerスレッドで処理されるのが2回目以上)となる。
conn_rec情報は、event_conn_state_t情報に既に持っているものをセットする。
sbh情報は、前回の処理時とは異なるスレッドで実行されると別の値になるので、改めてセットされている。
ここは同期型のリクエスト処理を行う場合の条件分岐になる。
event MPMは基本的には非同期型のリクエスト処理なので、ここには来ない。
httpd-2.4.6ではmod_sslのpre_connectionフック関数内の処理でclogging_input_filtersに1がセットされていたが、2.4.9では0だ。
process_connectionフック関数を実行する。
非同期の場合の経路なので、http_coreモジュールのprocess_connectionフック関数である
ap_process_http_connection() で実行されるのは ap_process_http_sync_connection()の方になる。
このラベルは、1017行目から使用されている。
既に読込可能なデータ(リクエスト等)がソケットに到着していた場合、
同じスレッドで同じconn_recを処理するので、直ちにここから処理を開始するために使用されている。
この場合1011行目の条件に該当しないので、c->abortedフラグは立っていない。
新規接続の受付か、Keep-Aliveな接続での次のリクエストの受付の場合の処理となる
Keep-Aliveな接続で次のリクエストを待っていて、TCPコネクション断(EOF)となった場合もここに来る。
process_connectionフック関数を実行する。
1件のリクエストの処理が、ここで行われる。
event MPMで、通常の処理なので、ap_process_http_async_connection()が実行される。
終了時にはcs->pub.stateは
のいずれかとなっている。
ap_process_http_async_connection()の最後で実行されるap_process_request_after_handler()の処理でCONN_STATE_WRITE_COMPLETION がセットされる。通常はこの状態だ。
httpd-2.4.9でCONN_STATE_SUSPENDEDを返す可能性があるのはmod_dialupだけのようだ。
ここでのCONN_STATE_LINGERは何らかのエラーで接続を閉じるべき状態となったケースだ。
pre_connectionフック関数の実行で問題があった場合はこの分岐に入る(926行目)。
正常にリクエスト処理を終えている場合はこの分岐に入る。
ここで、conn_recの出力フィルタ(コネクションフィルタ)の最下層のフィルタ(通常なら"CORE"出力フィルタ)に対して bucket brigade を NULLで引き渡し処理を行っている。
未処理のbucket brigadeが出力フィルタのコンテキストデータ内に保持されているのでこれを処理するためだと考えられる。
処理エラーの場合はCONN_STATE_LINGERが状態にセットされる。
出力フィルタが正常に処理終了して、conn_rec情報のdata_in_output_filtersが1の場合の処理
通常は、出力がブロックされて(ソケットへの書き込みが不可)、未送信のデータが残っている状態と考えられる。
この場合、書込み可能になるまで待機する。
4つのタイムアウトキュー(event MPMのタイムアウトキュー http://ohgrkrs-blog.blogspot.com/2014/04/event-mpm.html)
のうちのwrite_completion_qに対して、タイムアウトイベントを登録する。
また、listenerスレッドの監視対象のソケットにcs情報が持っているソケット(cs->pfd)を追加し、書込み可能になるのを監視する。
ここでは、接続の状態はWRITE_COMPLETION_STATEのままとなっている。
ここで、process_socket()関数の処理が終了する。
Keep-Alive接続でない場合、接続が異常な場合、プロセスの終了指示があった場合はこの分岐になる。
接続の状態にCONN_STATE_LINGERがセットされる。
読込可能なデータがあった場合がこの分岐になる。
ap_process_http_async_connection()関数の最後に実施されたap_process_request_after_handler()の処理でセットされている。
接続の状態をCONN_STATE_READ_REQUEST_LINEにする。
ただちに(この同じworkerスレッドの処理で)、処理を継続する。
リクエストパイプラインで、リクエストを順次送りつけてくる場合はこの経路になる。
また、通常のKeep-Alive処理でも、タイミングによっては(レスポンスを返すのと次のリクエストを受けるのが同時程度か、リクエストの方が早い状態)、この経路になる。
漠然とした印象としては、そんな場合にもいったん上位のイベント取得に戻されるべきのような気もするが、部分最適にはなっているのだろう。
それ以外の条件。
Keep-Alive接続だが、この時点では受信データが確認できていない場合だ。
接続の状態をCONN_STATE_CHECK_REQUEST_LINE_READABLEにする。
この分岐では、接続を終了する。
ここでは、csで扱われているソケットの書き込み停止が指示される(shutdown(SHUT_WR))。
処理中、問題が生じた場合にはソケットは閉じられ(close())、この関数の処理を抜ける(1026行目)。
ここでは、Keep-Alive接続上での次のリクエストの受信待ちを待機する。
4つのタイムアウトキュー(event MPMのタイムアウトキュー) のうちのkeepalive_qに対して、タイムアウトイベントを登録する。
また、listenerスレッドの監視対象のソケットにcs情報が持っているソケット(cs->pfd)を追加し、読込可能になるのを監視する。
接続の状態はCONN_STATE_CHECK_REQUEST_LINE_READABLEのままとなっている。
ここで、process_socket()関数の処理が終了する。
SUSPENDED状態の場合は、何も処理しない。
これで、process_socket関数は終了となる。
894 listener_poll_type *pt = apr_pcalloc(p, sizeof(*pt));
895 cs = apr_pcalloc(p, sizeof(event_conn_state_t));
apr_pcalloc()とapr_palloc()の違いは、
apr_pcalloc()ではapr_palloc()を行った後で確保した領域を0x0で初期化している。
896 cs->bucket_alloc = apr_bucket_alloc_create(p); 897 c = ap_run_create_connection(p, ap_server_conf, sock, 898 conn_id, sbh, cs->bucket_alloc);
create_connectionフック関数を実行する。
戻り値のcはconn_rec情報。
cs->bucket_allocは、生成されるconn_recのbucket_alloc情報にセットされる(core_create_conn()関数)。
bucket_allocはbucket brigadeの生成に使用するメモリの確保や再利用のために使用される。
利用済みのメモリなどは再利用可能なメモリ領域としてこのbucket_allocが管理する。
core_create_conn()はcoreモジュールが提供するcreate_connectionのフック関数だが、
httpdのソースに同梱されている中では、create_connectionフック関数はこのcore_create_conn()しか見当たらない。
(mod_example_hooks.cにも定義はあるが、有効な処理ではない)
(httpd-2.4.9/server/core.c) 4592 static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, 4593 apr_socket_t *csd, long id, void *sbh, 4594 apr_bucket_alloc_t *alloc) 4595 { 4596 apr_status_t rv; 4597 conn_rec *c = (conn_rec *) apr_pcalloc(ptrans, sizeof(conn_rec)); 4598 4599 c->sbh = sbh; 4600 (void)ap_update_child_status(c->sbh, SERVER_BUSY_READ, (request_rec *)NULL); 4601 4602 /* Got a connection structure, so initialize what fields we can 4603 * (the rest are zeroed out by pcalloc). 4604 */ 4605 c->conn_config = ap_create_conn_config(ptrans); 4606 c->notes = apr_table_make(ptrans, 5); 4607 4608 c->pool = ptrans; 4609 if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd)) 4610 != APR_SUCCESS) { 4611 ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00137) 4612 "apr_socket_addr_get(APR_LOCAL)"); 4613 apr_socket_close(csd); 4614 return NULL; 4615 } 4616 4617 apr_sockaddr_ip_get(&c->local_ip, c->local_addr); 4618 if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, csd)) 4619 != APR_SUCCESS) { 4620 ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00138) 4621 "apr_socket_addr_get(APR_REMOTE)"); 4622 apr_socket_close(csd); 4623 return NULL; 4624 } 4625 4626 apr_sockaddr_ip_get(&c->client_ip, c->client_addr); 4627 c->base_server = server; 4628 4629 c->id = id; 4630 c->bucket_alloc = alloc; 4631 4632 c->clogging_input_filters = 0; 4633 4634 return c; 4635 }
core_create_conn()では、conn_rec情報の初期値がいくつかセットされる。
まず、領域がapr_pcalloc()で確保されるので、領域全体は0x0で初期化される。
pool変数には第一引数のp(transactionメモリプール)がセットされる。
server_rec *base_server変数には第2引数のap_server_conf(メインサーバのserver_rec)がセットされる。
apr_sockaddr_t* 型のlocal_addrとclient_addrの各変数は第3引数の、sock(apr_socket_t情報)から取得し、セットされる。
char * 型変数の client_ipとlocal_ipも同様。
long idには、第4引数のconn_idがセットされる。
void *sbhには、第5引数のsbhがセットされる。
以下の 899行目からのif文は、create_connectionフック関数が失敗した場合の処理になる。
確保したbucket_allocを破棄し、transactionメモリプールをクリアする(clearの場合は、中身を消すだけで、プールそのものは残っている)。
そして、transactionメモリプールをworker_queue_infoに返している。
event MPMの子プロセスはworker_queue_infoでアイドル状態のワーカ数や再利用可能なtransactionメモリプールを管理している。
上記のcore_create_conn()の返り値を見ると、apr_socket_addr_get()が失敗してソケットを閉じた場合にこの経路に入っている(return NULL)。
ざっと眺めると、これはgetsockname()が失敗した場合に相当しそうだ。
ちゃんとしたソケットがlistenerスレッドから渡ってくるはずなので、メモリ不足などでも起きない限りこの経路には入りそうではない。
899 if (!c) { 900 apr_bucket_alloc_destroy(cs->bucket_alloc); 901 apr_pool_clear(p); 902 ap_push_pool(worker_queue_info, p); 903 return; 904 }
以下は、connection_count変数の加算を行い減算の準備をしている。
減算は、メモリプール(c->pool)を破棄するときに実行されるcleanup関数に登録している(906行目)。
connection_countはこのevent.cファイルのファイルスコープで定義された32ビット符号なし整数。
この子プロセスの持つ接続数が保持されることになる。
905 apr_atomic_inc32(&connection_count); 906 apr_pool_cleanup_register(c->pool, cs, decrement_connection_count, 907 apr_pool_cleanup_null);
conn_rec情報と、event_conn_state_t情報に初期設定を行われる。
908 c->current_thread = thd; 909 cs->c = c; 910 c->cs = &(cs->pub);
conn_rec構造体のcs変数には、cs->pubがセットされている。
911 cs->p = p;
912 cs->pfd.desc_type = APR_POLL_SOCKET;
913 cs->pfd.reqevents = APR_POLLIN;
914 cs->pfd.desc.s = sock;
915 pt->type = PT_CSD;
916 pt->baton = cs;
917 cs->pfd.client_data = pt;
918 TO_QUEUE_ELEM_INIT(cs);
919
920 ap_update_vhost_given_ip(c);
ソケットの接続情報(ローカルIPアドレス、ポート番号)をもとに、
対応するIPベースのVirtualHostの設定を確認し、あれば、その情報をconn_recのbase_server情報にセットする。
(初期設定はメインサーバになっている)
921
922 rc = ap_run_pre_connection(c, sock);
pre_connectionフック関数を実行する。
pre_connectionフック関数はcoreモジュール以外からもフック関数が登録される。
- coreモジュールが提供するcore_pre_connection()
- mod_sslが提供する ssl_hook_pre_connection()
- mod_logioが提供する logio_pre_conn()
- mod_dumpioが提供する dumpio_pre_conn()
最低限coreモジュールのフック関数だけ登録されていればHTTPリクエストは処理できる。
(httpd-2.4.9/server/core.c) 4637 static int core_pre_connection(conn_rec *c, void *csd)
第1引数はconn_rec情報、第2引数はapr_socket_t情報が与えられている。
4638 {
4639 core_net_rec *net = apr_palloc(c->pool, sizeof(*net));
4640 apr_status_t rv;
4641
4642 /* The Nagle algorithm says that we should delay sending partial
4643 * packets in hopes of getting more data. We don't want to do
4644 * this; we are not telnet. There are bad interactions between
4645 * persistent connections and Nagle's algorithm that have very severe
4646 * performance penalties. (Failing to disable Nagle is not much of a
4647 * problem with simple HTTP.)
4648 */
4649 rv = apr_socket_opt_set(csd, APR_TCP_NODELAY, 1);
ソケットに対するTCP_NODELAYオプションを有効にする。
これはデフォルトで有効なっているNagle(ネーグル)アルゴリズムを無効にする。
Nagleアルゴリズムは、細かい点はさておいて、データの送信時にあまり小さなデータをそのまま送信するのではなく、ある程度まとめて送信するために
少し待つ。この結果、小さいデータを細切れのパケットで送るのではなく、大きめのデータをひとつのパケットで送ることができる。
これにはネットワーク帯域の利用効率を高める効果がある。
これを無効にするということは、小さなデータであっても直ちに送信する、ということになる。
コメントにある、持続的(Keep-Alive)接続とNagleアルゴリズムの悪い相互作用というのは、おそらく、Apache httpdがレスポンスを返すとき、
何度か送信する最後のレスポンスの断片の送信処理で、十分なサイズのデータが残っていなければ、Nagleアルゴリズムによって送信が遅延し、
つまり、リクエスト/レスポンスのやり取りの完了が遅延してしまう可能性があるということではないか。
(ソケットを閉じるなら、未送信の小さなデータがあっても送信される)
4650 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
4651 /* expected cause is that the client disconnected already,
4652 * hence the debug level
4653 */
4654 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00139)
4655 "apr_socket_opt_set(APR_TCP_NODELAY)");
エラーの場合は、DEBUGログを出力するだけ。
4656 }
4657
4658 /* The core filter requires the timeout mode to be set, which
4659 * incidentally sets the socket to be nonblocking. If this
4660 * is not initialized correctly, Linux - for example - will
4661 * be initially blocking, while Solaris will be non blocking
4662 * and any initial read will fail.
4663 */
4664 rv = apr_socket_timeout_set(csd, c->base_server->timeout);
タイムアウトを設定する。
ここでは、apr_socket_t構造体(*csd)のtimeout変数に c->base_server->timeoutをセットする。
4665 if (rv != APR_SUCCESS) { 4666 /* expected cause is that the client disconnected already */ 4667 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00140) 4668 "apr_socket_timeout_set"); 4669 } 4670
core_net_rec構造体 *net に値をセットする。
4671 net->c = c;
4672 net->in_ctx = NULL;
4673 net->out_ctx = NULL;
4674 net->client_socket = csd;
4675
4676 ap_set_core_module_config(net->c->conn_config, csd);
csdをconn_recのconn_configのcoreモジュール情報領域に登録する。
4677 ap_add_input_filter_handle(ap_core_input_filter_handle, net, NULL, net->c);
入力フィルタ ap_core_input_filter_handle("CORE_IN")を登録する。
実体は ap_core_input_filter()関数だ。
net情報がフィルタ処理のコンテキスト情報として渡されている。
4678 ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c);
出力フィルタ ap_core_output_filter_handle("CORE")を登録する。
実体は ap_core_output_filter()関数だ。
こちらでも、net情報がフィルタ処理のコンテキスト情報として渡されている。
4679 return DONE; 4680 }
mod_sslのpre_connectionフック関数ではHTTPSリクエスト処理のために必要なフィルタが追加されるが、これは改めて確認して書くことにする。
以下の923行目からのif文は、pre_connectionフック関数が失敗した場合の処理だ。
ただし、ここでは、conn_recのabortedフラグに1をセットするだけで、関数処理が終了していない。
create_connectionフック関数の失敗時とは異なっている。
923 if (rc != OK && rc != DONE) { 924 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469) 925 "process_socket: connection aborted"); 926 c->aborted = 1; 927 } 928 929 /** 930 * XXX If the platform does not have a usable way of bundling 931 * accept() with a socket readability check, like Win32, 932 * and there are measurable delays before the 933 * socket is readable due to the first data packet arriving, 934 * it might be better to create the cs on the listener thread 935 * with the state set to CONN_STATE_CHECK_REQUEST_LINE_READABLE 936 * 937 * FreeBSD users will want to enable the HTTP accept filter 938 * module in their kernel for the highest performance 939 * When the accept filter is active, sockets are kept in the 940 * kernel until a HTTP request is received. 941 */ 942 cs->pub.state = CONN_STATE_READ_REQUEST_LINE; 943 944 cs->pub.sense = CONN_SENSE_DEFAULT;
942行目で接続状態の初期値がセットされる(接続を受け付けた状態)。
cs->pubの値は、作成したconn_rec情報のcs変数で参照できる。
ここまでで、conn_rec情報のセットアップが完了する。
945 } 946 else {
こちらはKeepAlive接続の場合や、書込み可能が検知された場合の条件(この接続がworkerスレッドで処理されるのが2回目以上)となる。
conn_rec情報は、event_conn_state_t情報に既に持っているものをセットする。
sbh情報は、前回の処理時とは異なるスレッドで実行されると別の値になるので、改めてセットされている。
947 c = cs->c; 948 c->sbh = sbh; 949 c->current_thread = thd; 950 }
951
952 if (c->clogging_input_filters && !c->aborted) {
ここは同期型のリクエスト処理を行う場合の条件分岐になる。
event MPMは基本的には非同期型のリクエスト処理なので、ここには来ない。
httpd-2.4.6ではmod_sslのpre_connectionフック関数内の処理でclogging_input_filtersに1がセットされていたが、2.4.9では0だ。
953 /* Since we have an input filter which 'clogs' the input stream,
954 * like mod_ssl used to, lets just do the normal read from input
955 * filters, like the Worker MPM does. Filters that need to write
956 * where they would otherwise read, or read where they would
957 * otherwise write, should set the sense appropriately.
958 */
959 apr_atomic_inc32(&clogged_count);
960 ap_run_process_connection(c);
process_connectionフック関数を実行する。
非同期の場合の経路なので、http_coreモジュールのprocess_connectionフック関数である
ap_process_http_connection() で実行されるのは ap_process_http_sync_connection()の方になる。
961 if (cs->pub.state != CONN_STATE_SUSPENDED) { 962 cs->pub.state = CONN_STATE_LINGER; 963 } 964 apr_atomic_dec32(&clogged_count); 965 } 966 967 read_request:
このラベルは、1017行目から使用されている。
既に読込可能なデータ(リクエスト等)がソケットに到着していた場合、
同じスレッドで同じconn_recを処理するので、直ちにここから処理を開始するために使用されている。
この場合1011行目の条件に該当しないので、c->abortedフラグは立っていない。
968 if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE) {
新規接続の受付か、Keep-Aliveな接続での次のリクエストの受付の場合の処理となる
Keep-Aliveな接続で次のリクエストを待っていて、TCPコネクション断(EOF)となった場合もここに来る。
969 if (!c->aborted) { 970 ap_run_process_connection(c);
process_connectionフック関数を実行する。
1件のリクエストの処理が、ここで行われる。
event MPMで、通常の処理なので、ap_process_http_async_connection()が実行される。
終了時にはcs->pub.stateは
- CONN_STATE_WRITE_COMPLETION
- CONN_STATE_SUSPENDED
- CONN_STATE_LINGER
のいずれかとなっている。
ap_process_http_async_connection()の最後で実行されるap_process_request_after_handler()の処理でCONN_STATE_WRITE_COMPLETION がセットされる。通常はこの状態だ。
httpd-2.4.9でCONN_STATE_SUSPENDEDを返す可能性があるのはmod_dialupだけのようだ。
ここでのCONN_STATE_LINGERは何らかのエラーで接続を閉じるべき状態となったケースだ。
971 972 /* state will be updated upon return 973 * fall thru to either wait for readability/timeout or 974 * do lingering close 975 */ 976 } 977 else {
pre_connectionフック関数の実行で問題があった場合はこの分岐に入る(926行目)。
978 cs->pub.state = CONN_STATE_LINGER; 979 } 980 } 981 982 if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) {
正常にリクエスト処理を終えている場合はこの分岐に入る。
ここで、conn_recの出力フィルタ(コネクションフィルタ)の最下層のフィルタ(通常なら"CORE"出力フィルタ)に対して bucket brigade を NULLで引き渡し処理を行っている。
未処理のbucket brigadeが出力フィルタのコンテキストデータ内に保持されているのでこれを処理するためだと考えられる。
983 ap_filter_t *output_filter = c->output_filters; 984 apr_status_t rv; 985 ap_update_child_status_from_conn(sbh, SERVER_BUSY_WRITE, c); 986 while (output_filter->next != NULL) { 987 output_filter = output_filter->next; 988 } 989 rv = output_filter->frec->filter_func.out_func(output_filter, NULL); 990 if (rv != APR_SUCCESS) {
処理エラーの場合はCONN_STATE_LINGERが状態にセットされる。
991 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(00470) 992 "network write failure in core output filter"); 993 cs->pub.state = CONN_STATE_LINGER; 994 } 995 else if (c->data_in_output_filters) {
出力フィルタが正常に処理終了して、conn_rec情報のdata_in_output_filtersが1の場合の処理
通常は、出力がブロックされて(ソケットへの書き込みが不可)、未送信のデータが残っている状態と考えられる。
996 /* Still in WRITE_COMPLETION_STATE: 997 * Set a write timeout for this connection, and let the 998 * event thread poll for writeability. 999 */
この場合、書込み可能になるまで待機する。
4つのタイムアウトキュー(event MPMのタイムアウトキュー http://ohgrkrs-blog.blogspot.com/2014/04/event-mpm.html)
のうちのwrite_completion_qに対して、タイムアウトイベントを登録する。
また、listenerスレッドの監視対象のソケットにcs情報が持っているソケット(cs->pfd)を追加し、書込み可能になるのを監視する。
1000 cs->expiration_time = ap_server_conf->timeout + apr_time_now(); 1001 apr_thread_mutex_lock(timeout_mutex); 1002 TO_QUEUE_APPEND(write_completion_q, cs); 1003 cs->pfd.reqevents = ( 1004 cs->pub.sense == CONN_SENSE_WANT_READ ? APR_POLLIN : 1005 APR_POLLOUT) | APR_POLLHUP | APR_POLLERR; 1006 cs->pub.sense = CONN_SENSE_DEFAULT; 1007 rc = apr_pollset_add(event_pollset, &cs->pfd); 1008 apr_thread_mutex_unlock(timeout_mutex); 1009 return;
ここでは、接続の状態はWRITE_COMPLETION_STATEのままとなっている。
ここで、process_socket()関数の処理が終了する。
1010 } 1011 else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted || 1012 listener_may_exit) {
Keep-Alive接続でない場合、接続が異常な場合、プロセスの終了指示があった場合はこの分岐になる。
1013 cs->pub.state = CONN_STATE_LINGER;
接続の状態にCONN_STATE_LINGERがセットされる。
1014 } 1015 else if (c->data_in_input_filters) {
読込可能なデータがあった場合がこの分岐になる。
ap_process_http_async_connection()関数の最後に実施されたap_process_request_after_handler()の処理でセットされている。
1016 cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
接続の状態をCONN_STATE_READ_REQUEST_LINEにする。
1017 goto read_request;
ただちに(この同じworkerスレッドの処理で)、処理を継続する。
リクエストパイプラインで、リクエストを順次送りつけてくる場合はこの経路になる。
また、通常のKeep-Alive処理でも、タイミングによっては(レスポンスを返すのと次のリクエストを受けるのが同時程度か、リクエストの方が早い状態)、この経路になる。
漠然とした印象としては、そんな場合にもいったん上位のイベント取得に戻されるべきのような気もするが、部分最適にはなっているのだろう。
1018 } 1019 else {
それ以外の条件。
Keep-Alive接続だが、この時点では受信データが確認できていない場合だ。
1020 cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE;
接続の状態をCONN_STATE_CHECK_REQUEST_LINE_READABLEにする。
1021 }
1022 }
1023
1024 if (cs->pub.state == CONN_STATE_LINGER) {
この分岐では、接続を終了する。
1025 if (!start_lingering_close_blocking(cs))
ここでは、csで扱われているソケットの書き込み停止が指示される(shutdown(SHUT_WR))。
処理中、問題が生じた場合にはソケットは閉じられ(close())、この関数の処理を抜ける(1026行目)。
1026 return; 1027 } 1028 else if (cs->pub.state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { 1029 /* It greatly simplifies the logic to use a single timeout value here 1030 * because the new element can just be added to the end of the list and 1031 * it will stay sorted in expiration time sequence. If brand new 1032 * sockets are sent to the event thread for a readability check, this 1033 * will be a slight behavior change - they use the non-keepalive 1034 * timeout today. With a normal client, the socket will be readable in 1035 * a few milliseconds anyway. 1036 */
ここでは、Keep-Alive接続上での次のリクエストの受信待ちを待機する。
4つのタイムアウトキュー(event MPMのタイムアウトキュー) のうちのkeepalive_qに対して、タイムアウトイベントを登録する。
また、listenerスレッドの監視対象のソケットにcs情報が持っているソケット(cs->pfd)を追加し、読込可能になるのを監視する。
1037 cs->expiration_time = ap_server_conf->keep_alive_timeout + 1038 apr_time_now(); 1039 c->sbh = NULL; 1040 apr_thread_mutex_lock(timeout_mutex); 1041 TO_QUEUE_APPEND(keepalive_q, cs); 1042 1043 /* Add work to pollset. */ 1044 cs->pfd.reqevents = APR_POLLIN; 1045 rc = apr_pollset_add(event_pollset, &cs->pfd); 1046 apr_thread_mutex_unlock(timeout_mutex); 1047 1048 if (rc != APR_SUCCESS) { 1049 ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, 1050 "process_socket: apr_pollset_add failure"); 1051 AP_DEBUG_ASSERT(rc == APR_SUCCESS); 1052 } 1053 return;
接続の状態はCONN_STATE_CHECK_REQUEST_LINE_READABLEのままとなっている。
ここで、process_socket()関数の処理が終了する。
1054 }
1055 else if (cs->pub.state == CONN_STATE_SUSPENDED) {
SUSPENDED状態の場合は、何も処理しない。
1056 apr_atomic_inc32(&suspended_count); 1057 } 1058 /* 1059 * Prevent this connection from writing to our connection state after it 1060 * is no longer associated with this thread. This would happen if the EOR 1061 * bucket is destroyed from the listener thread due to a connection abort 1062 * or timeout. 1063 */ 1064 c->sbh = NULL; 1065 return; 1066 }
これで、process_socket関数は終了となる。
0 件のコメント:
コメントを投稿