(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)
884 {
:
1024 if (cs->pub.state == CONN_STATE_LINGER) {
1025 if (!start_lingering_close_blocking(cs))
1026 return;
1027 }
1025行目のstart_lingering_close_blocking()が、ソケットを閉じる処理(shutdown(SHUT_WR))を行う。
引数はcs(event_conn_state_t情報)のみだが、cs->cは、conn_rec情報を持っている。
(9) start_lingering_close_blocking
(httpd-2.4.9/server/mpm/event/event.c) 813 /* 814 * Close our side of the connection, flushing data to the client first. 815 * Pre-condition: cs is not in any timeout queue and not in the pollset, 816 * timeout_mutex is not locked 817 * return: 0 if connection is fully closed, 818 * 1 if connection is lingering 819 * May only be called by worker thread. 820 */ 821 static int start_lingering_close_blocking(event_conn_state_t *cs) 822 { 823 if (ap_start_lingering_close(cs->c)) { 824 apr_pool_clear(cs->p); 825 ap_push_pool(worker_queue_info, cs->p); 826 return 0; 827 } 828 return start_lingering_close_common(cs); 829 }
大きく2つの処理からなっている。
- ap_start_lingering_close()
shutdown(SHUT_WR)を実行する - start_lingering_close_common()
LINGER待ちタイマーに登録され、ソケットをlistenerスレッドの監視対象に追加する。
- 1は、正常に処理
- 0は、何らかの不具合が発生した場合
823行目のap_start_lingering_close()が失敗した場合は、
transactionメモリプールを返す処理がここで行われ(上記824~825行目)、return 0で返る。
一方、828行目のstart_lingering_close_common()が0を返す場合は、
start_lingering_close_common()関数内で同等の処理が実行済みとなっている(下記の806~807行め)。
2つの関数の内容を順に見てみる。
(10) ap_start_lingering_close
(httpd-2.4.9/server/connection.c) 95 #define SECONDS_TO_LINGER 2 96 97 AP_DECLARE(int) ap_start_lingering_close(conn_rec *c) 98 { 99 apr_socket_t *csd = ap_get_conn_socket(c);
conn_rec構造体のconn_configが持っているcore情報にある、apr_socket_t情報を取得している。
遡ってみると、この情報は、core_pre_connection()関数の4676行めでセットされている。
100 101 if (!csd) { 102 return 1;
apr_socket_t情報が取得できない場合は、何もしないで戻る。
shutdown()もできないので、監視対象にもならない。
103 }
104
105 if (c->sbh) {
106 ap_update_child_status(c->sbh, SERVER_CLOSING, NULL);
これはスコアボードのステータスを更新している。
107 }
108
:
115 /* Close the connection, being careful to send out whatever is still
116 * in our buffers. If possible, try to avoid a hard close until the
117 * client has ACKed our FIN and/or has stopped sending us data.
118 */
119
120 /* Send any leftover data to the client, but never try to again */
121 ap_flush_conn(c);
ap_flush_conn()関数では、次の処理が行われる。
- bucket brigadeを作成。
- FLUSHメタデータバケットを作成し、bucket brigadeに追加
- bucket brigadeを conn_recのoutput_filtersに引き渡す(ap_pass_brigade)。
122 123 if (c->aborted) {
この分岐は処理異常時。
この分岐ではソケットを閉じてしまう。
124 apr_socket_close(csd);
apr_socket_closeでは、次の処理が行われる。
(apr/network_io/unix/sockets.c) 172 apr_status_t apr_socket_close(apr_socket_t *thesocket) 173 { 174 return apr_pool_cleanup_run(thesocket->pool, thesocket, socket_cleanup); 175 }
これは、socket_cleanup(thesocket)を行うということだ。
ただし、その前に、thesocket->poolにプールを削除する際に自動実行するように登録していたsocket_cleanup(thesocket)の情報を削除している。
この登録処理はapr_socket_create()やapr_socket_accept()等々でapr_socket_t情報領域が作成された際に、そのメモリを確保したメモリプールに対して自動的に行われている。
放っておいてもこのメモリプールが破棄されるときには、実行されるが、ここではメモリ領域を破棄しないで、socket_cleanup()だけを実行したいということだ。
125 return 1;
126 }
127
128 /* Shut down the socket for write, which will send a FIN
129 * to the peer.
130 */
131 if (apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS
この関数は、shutdown()関数を実行している。APR_SHUTDOWN_WRITEは、1(SHUT_WR)。
(apr/network_io/unix/sockets.c) 166 apr_status_t apr_socket_shutdown(apr_socket_t *thesocket, 167 apr_shutdown_how_e how) 168 { 169 return (shutdown(thesocket->socketdes, how) == -1) ? errno : APR_SUCCESS; 170 }
shutdown(SHUT_WR)を行うと、私の知る限りは、FINが通知される。
自身のソケットはFIN-WAIT(FIN-WAIT-2)状態になり、通信相手のソケットはFINを受け取るので、CLOSE-WAITになる。
通信相手(ブラウザ側)は、CLOSE-WAITであっても、データの送信が可能な状態だ。
通常、この通信相手側で適切にデータ受信を行えば、ここの処理で行われたFIN通知、つまり通信断(EOF)が検出できるはずである。そうすると、相手側でも通信を終了するはずだ。
こちら側でshutdown(SHUT_WR)を行った後、通信相手も通信終了の処理を行って(相手からFINが届いて)、それを処理してからソケットのclose()を実行したいというのが、この後の処理だ。
132 || c->aborted) {
c->abortedはあちこちで見られる、コネクション異常を示すフラグだ。
このフラグが立っていると、コネクションは再利用されない。
つまり、shutdown(SHUT_WR)に失敗したか、コネクションが異常な場合に、この分岐に入る。
133 apr_socket_close(csd);
apr_socket_close()は、124行目で見た。
134 return 1; 135 } 136 137 return 0;
この関数は正常終了時に0が返り、何らかの異常時には1が返る。
138 }
なお、apr_socket_close()で実行される socket_cleanup()関数は次の通り、close()を実行している。
(apr/network_io/unix/sockets.c) 31 static apr_status_t socket_cleanup(void *sock) 32 { 33 apr_socket_t *thesocket = sock; 34 int sd = thesocket->socketdes; 35 36 /* Set socket descriptor to -1 before close(), so that there is no 37 * chance of returning an already closed FD from apr_os_sock_get(). 38 */ 39 thesocket->socketdes = -1; 40 41 if (close(sd) == 0) { 42 return APR_SUCCESS; 43 } 44 else { 45 /* Restore, close() was not successful. */ 46 thesocket->socketdes = sd; 47 48 return errno; 49 } 50 }
(11) start_lingering_close_common
(server/mpm/event/event.c) 760 static int start_lingering_close_common(event_conn_state_t *cs) 761 { 762 apr_status_t rv; 763 struct timeout_queue *q; 764 apr_socket_t *csd = cs->pfd.desc.s; : 771 apr_socket_timeout_set(csd, 0);
ソケットのタイムアウト値を0に更新し、 イベントを待たずに、すぐにタイムアウトするようにしている。
:
773 /*
774 * If some module requested a shortened waiting period, only wait for
775 * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain
776 * DoS attacks.
777 */
778 if (apr_table_get(cs->c->notes, "short-lingering-close")) {
cs->cにはconn_rec情報が入っている。 conn_recのnotes配列に"short-lingering-close"が登録されている場合がこの経路となる。
その場合、event MPMのタイムアウトキューの4つのタイムアウトキューのうちのshort_linger_qにクローズ待ちが登録される。
grepすると、mod_reqtimeoutモジュールでこのキーワードがセットされているのが見つかるが、他にはないようだ。
779 cs->expiration_time =
780 apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER);
781 q = &short_linger_q;
782 cs->pub.state = CONN_STATE_LINGER_SHORT;
783 }
784 else {
上記以外の場合、linger_qにクローズ待ちが登録される。
タイムアウトまでの待ち時間が異なっている。
785 cs->expiration_time =
786 apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER);
787 q = &linger_q;
788 cs->pub.state = CONN_STATE_LINGER_NORMAL;
789 }
以下、LINGER待ちをタイムアウトキューに登録して、このソケットをlistenerスレッドの監視対象に加える。
cs->pub.senseによって読み込みか書き出しかを判定している。
この関数自体は他の箇所からも利用されているが、process_socket() -> start_lingering_close_blocking()では、APR_POLLINだろう。
790 apr_atomic_inc32(&lingering_count); 791 apr_thread_mutex_lock(timeout_mutex); 792 TO_QUEUE_APPEND(*q, cs); 793 cs->pfd.reqevents = ( 794 cs->pub.sense == CONN_SENSE_WANT_WRITE ? APR_POLLOUT : 795 APR_POLLIN) | APR_POLLHUP | APR_POLLERR; 796 cs->pub.sense = CONN_SENSE_DEFAULT; 797 rv = apr_pollset_add(event_pollset, &cs->pfd); 798 apr_thread_mutex_unlock(timeout_mutex); 799 if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { 800 ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, 801 "start_lingering_close: apr_pollset_add failure"); 802 apr_thread_mutex_lock(timeout_mutex); 803 TO_QUEUE_REMOVE(*q, cs); 804 apr_thread_mutex_unlock(timeout_mutex); 805 apr_socket_close(cs->pfd.desc.s); 806 apr_pool_clear(cs->p); 807 ap_push_pool(worker_queue_info, cs->p); 808 return 0; 809 } 810 return 1;
この関数では、正常終了が1、何らかのエラーの場合は0が返っている。
811 }
(12) worker MPM: process_socket
worker MPMを比較のために見てみると
process_socket()はシンプルだ。
リクエストを処理するという機能は、event MPMと変わらないが、
worker MPMでは、Keep-Aliveなリクエストの場合、その接続で扱われるリクエストがこのprocess_socket内で継続的に処理される(接続を切るまでこの関数から復帰しない)という特徴がある。
(httpd-2.4.9/server/connection.c) 605 static void process_socket(apr_thread_t *thd, apr_pool_t *p, apr_socket_t *sock, 606 int my_child_num, 607 int my_thread_num, apr_bucket_alloc_t *bucket_alloc) 608 { 609 conn_rec *current_conn; 610 long conn_id = ID_FROM_CHILD_THREAD(my_child_num, my_thread_num); 611 ap_sb_handle_t *sbh; 612 613 ap_create_sb_handle(&sbh, p, my_child_num, my_thread_num); 614 615 current_conn = ap_run_create_connection(p, ap_server_conf, sock, 616 conn_id, sbh, bucket_alloc); 617 if (current_conn) { 618 current_conn->current_thread = thd; 619 ap_process_connection(current_conn, sock); 620 ap_lingering_close(current_conn); 621 } 622 }
619行目のap_process_connection()は、ap_run_pre_connection()処理と、ap_run_process_connection()処理が行われている。
event MPMでは、ap_run_pre_connection()処理は、event_conn_state_t情報が未設定の場合にのみ実行されるようになっている。
worker MPMでは、その情報は常に未設定な状態に相当する。
ap_run_process_connection()は、procses_connectionフック関数の実行だ。既にevent MPMで見た。 フック関数の実体は、ap_process_http_connection()だった。
これは非同期型かそうでないかで呼び出す関数が分かれていた。worker MPMは同期型となる。したがって、ここで実行される関数がevent MPMとは異なる。 ap_process_http_sync_connection()だ。
これは追いかけないが、Keep-Alive状態の処理が繰り返されるのは、この処理の違いによる。
続く620行めに、ap_lingering_close()という処理がある。
リクエスト処理後には、必ず実行される処理だ。
これを見ると、shutdown(SHUT_WR)を実行後(FINが対向に送信される)、FIN受信まで最大で30秒間データを受信し続ける可能性があるようになっている。
event MPMでは、この処理は実行されずに、shutdown(SHUT_WR)を行うと、タイムアウトキューにタイムアウトを登録して、ソケットをlistenerスレッドの監視対象に追加している。
つまり、同様の処理は、別の処理として切り出されている。
通常、この経路ではコネクションを閉じるので、クライアントはレスポンス受信後、直ちにソケットを閉じるだろうから、あまり待つことなく、処理は終わるはずだ。
それでも、クライアントがレスポンスを受信し終わり、処理を正常に終えるまでこの処理内でブロックしているということの影響が多少はあるかもしれない。
event MPM と worker MPM の比較まとめではKeep-Alive無効時にもevent MPMの方が性能が優位なのはこのあたりも影響しているのではないか、と書いた。
以下、この処理の内容を見る。
(13) ap_lingering_close
(httpd-2.4.9/server/connection.c) 140 AP_DECLARE(void) ap_lingering_close(conn_rec *c) 141 { 142 char dummybuf[512]; 143 apr_size_t nbytes; 144 apr_time_t timeup = 0; 145 apr_socket_t *csd = ap_get_conn_socket(c); 146 147 if (ap_start_lingering_close(c)) {
これは、event MPMでもstart_lingering_close_blocking()関数で呼ばれていた処理だ。
正常の場合は、shutdown(SHUT_WR)が実行され、0で返る。
問題があれば、ソケットはclose()されて、1で返る。
148 return;
ap_start_lingering_close()で問題があれば、ここでreturnする。
149 } 150 151 /* Read available data from the client whilst it continues sending 152 * it, for a maximum time of MAX_SECS_TO_LINGER. If the client 153 * does not send any data within 2 seconds (a value pulled from 154 * Apache 1.3 which seems to work well), give up. 155 */ 156 apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER)); 157 apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 1);
ソケットタイムアウト時間をSECONDS_TO_LINGER(2秒)に更新し、
ソケットオプションでAPR_INCOMPLETE_READをセットし、
以下の処理が行われる。
この設定は、165行目の apr_socket_recv()の挙動に影響する。
158 159 /* The common path here is that the initial apr_socket_recv() call 160 * will return 0 bytes read; so that case must avoid the expensive 161 * apr_time_now() call and time arithmetic. */ 162 163 do { 164 nbytes = sizeof(dummybuf); 165 if (apr_socket_recv(csd, dummybuf, &nbytes) || nbytes == 0)
ソケットを読み込んで正常にデータが受信されなかった場合(タイムアウトかエラー)、
EOFを検知した場合、
このif文にはいる(breakする)。
166 break;
データを受信した場合、処理が継続される。
167 168 if (timeup == 0) { 169 /* 170 * First time through; 171 * calculate now + 30 seconds (MAX_SECS_TO_LINGER). 172 * 173 * If some module requested a shortened waiting period, only wait for 174 * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain 175 * DoS attacks. 176 */
conn_recのnote配列に"short-lingering-close"がセットされていた場合には、
現在時刻からSECONDS_TO_LINGER(2)秒後がtimeupにセットされる。
それ以外の場合には、
現在時刻からMAX_SECS_TO_LINGER(30)秒後がtimeupにセットされる。
177 if (apr_table_get(c->notes, "short-lingering-close")) { 178 timeup = apr_time_now() + apr_time_from_sec(SECONDS_TO_LINGER); 179 } 180 else { 181 timeup = apr_time_now() + apr_time_from_sec(MAX_SECS_TO_LINGER); 182 } 183 continue; 184 } 185 } while (apr_time_now() < timeup);
データ受信が続く場合は、timeupになるまで受信が続けられる。
186
187 apr_socket_close(csd);
最後に、ソケットがclose()される。
188 return; 189 }
worker MPMでは、このソケットのclose()までがprocess_socket()処理内で実行された。
これはここまで。
event MPMで、shutdown(SHUT_WR)を行ったソケットの処理の続きはlistenerスレッド側で行われている。
そこで、listenerスレッドの処理を少し見ておきたい。
今回はここまで。
0 件のコメント:
コメントを投稿