2015年1月19日月曜日

CORE出力フィルタ: ソケット送信処理(3) sendfile系

CORE出力フィルタ: ソケット送信処理(1) の続きで今回はsendfile()系の
(1) sendfile_nonblocking()
とそこから呼ばれるAPRライブラリ関数の
(2) apr_socket_sendfile()
を見る

(1)sendfile_nonblocking関数

FILEデータバケットをapr_socket_sendfile()を使って送信するための関数となる。
apr_socket_sendfile()は本関数で1度実行される。
実行前に、ソケットのタイムアウト設定を0にし、実行後に元の値に戻す処理が行われている。

apr_socket_sendfile()実行後、ファイル全体の送信ができたか、送信サイズをファイルサイズを比較し、一致した場合には、送信されたFILEバケットをbucket brigadeから取り外し、削除(destroy)する。
未送信データが残っていた場合には、FILEバケットを送信済みデータ部分と未送信データ部分に
bucket分割し(split)、送信済み分のFILEバケットを取り外して、削除(destroy)する。
送信済みデータサイズは引数 *cumulative_bytes_writtenに加えられる。

    834 static apr_status_t sendfile_nonblocking(apr_socket_t *s,
    835                                          apr_bucket *bucket,
    836                                          apr_size_t *cumulative_bytes_written,
    837                                          conn_rec *c)
    838 {
    839     apr_status_t rv = APR_SUCCESS;
    840     apr_bucket_file *file_bucket;
    841     apr_file_t *fd;
    842     apr_size_t file_length;
    843     apr_off_t file_offset;
    844     apr_size_t bytes_written = 0;
    845
    846     if (!APR_BUCKET_IS_FILE(bucket)) {

FILEデータバケットでなければ、エラーで返る。

    847         ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006)
    848                      "core_filter: sendfile_nonblocking: "
    849                      "this should never happen");
    850         return APR_EGENERAL;
    851     }
    852     file_bucket = (apr_bucket_file *)(bucket->data);

FILEデータバケットのタイプ依存情報)apr_bucket_file)を取得する

    853     fd = file_bucket->fd;

apr_bucket_fileの持つファイル情報(apt_file_t)を取得する

    854     file_length = bucket->length;
    855     file_offset = bucket->start;

ファイルの参照する領域(開始位置とサイズ)をbucket情報から取得する

    856
    857     if (bytes_written < file_length) {

ここで、apr_socket_sendfile()が実行される。
実行前にタイムアウトが0にされるのは非ブロック処理とするため。

    858         apr_size_t n = file_length - bytes_written;
    859         apr_status_t arv;
    860         apr_interval_time_t old_timeout;
    861
    862         arv = apr_socket_timeout_get(s, &old_timeout);

現在セットされているタイムアウトの設定値をold_timeoutに退避する。

    863         if (arv != APR_SUCCESS) {
    864             return arv;
    865         }
    866         arv = apr_socket_timeout_set(s, 0);

タイムアウトの設定を0にする。

    867         if (arv != APR_SUCCESS) {
    868             return arv;
    869         }
    870         rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0);

apr_socket_sendfile()を実行する。

    871         if (rv == APR_SUCCESS) {

成功すると、書込み済みバイト数を格納するbytes_writtenにnを加え、
開始位置であるfile_offsetがnバイト進められる。
しかし、この値はこの関数内では使われないので、この変更は意味が分からない。


    872             bytes_written += n;
    873             file_offset += n;
    874         }
    875         arv = apr_socket_timeout_set(s, old_timeout);

タイムアウトの設定をこの処理実行前の値に戻す。

    876         if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
    877             rv = arv;
    878         }
    879     }
    880     if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {

ap__logio_add_bytes_outはlogioモジュールが提供するオプション関数で、
ここでは、ログ書式%Oのための出力バイト数を集計している。

    881         ap__logio_add_bytes_out(c, bytes_written);
    882     }
    883     *cumulative_bytes_written += bytes_written;

呼び出し元に返却用に、出力済みバイト数を*cumulative_bytes_writtenに加える。

    884     if ((bytes_written < file_length) && (bytes_written > 0)) {

出力したバイト数が、送信する予定だったファイルサイズより小さい場合

    885         apr_bucket_split(bucket, bytes_written);

送信済みの範囲と未送信の範囲にFILEデータバケットを分割(split)する。
apr_file_t 情報と FILEデータバケット (2.2)split関数 参照。

    886         APR_BUCKET_REMOVE(bucket);

送信済みのFILEデータバケットをbucket brigadeから外す。

    887         apr_bucket_destroy(bucket);

送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット  (2.3)destroy関数参照。

    888     }
    889     else if (bytes_written == file_length) {

出力したバイト数と送信する予定だったファイルサイズが一致した場合

    890         APR_BUCKET_REMOVE(bucket);

送信済みのFILEデータバケットをbucket brigadeから外す。

    891         apr_bucket_destroy(bucket);

送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット (2.3)destroy関数参照。

    892     }
    893     return rv;
    894 }


(2)apr_socket_sendfile

ちなみに、64bit環境で確認している。
ヘッダ・フッターといった独自のデータ構造(apr_hdtr_t)を扱っており、
コード上、sendfile()の実行前後でこのデータが送信されているようだが、
sendfile_nonblocking()から呼ばれている場合には、この情報はNULL固定で、利用されていない。

(apr/network_io/unix/sendrecv.c)

    257 apr_status_t apr_socket_sendfile(apr_socket_t *sock, apr_file_t *file,
    258                                  apr_hdtr_t *hdtr, apr_off_t *offset,
    259                                  apr_size_t *len, apr_int32_t flags)
    260 {
    261     int rv, nbytes = 0, total_hdrbytes, i;
    262     apr_status_t arv;
    263
    264 #if APR_HAS_LARGE_FILES && defined(HAVE_SENDFILE64)
     :
    268 #elif APR_HAS_LARGE_FILES && SIZEOF_OFF_T == 4
     :

    279 #else
    280     off_t off = *offset;
    281
    282     /* Multiple reports have shown sendfile failing with EINVAL if
    283      * passed a >=2Gb count value on some 64-bit kernels.  It won't
    284      * noticably hurt performance to limit each call to <2Gb at a
    285      * time, so avoid that issue here: */

一部の64bitカーネルにおいて、 2GB以上の値を渡すと、sendfileが失敗するという報告が複数ある。 一度の呼び出しでの上限を2GB未満に制限しても性能に悪影響は少ないだろうと考えられるので、 ここでその問題を回避している

    286     if (sizeof(off_t) == 8 && *len > INT_MAX) {

INT_MAXは2GB-1(0x7fffffff)だ。 一度に送信するデータ長の上限をINT_MAXに制限している。

    287         *len = INT_MAX;
    288     }
    289 #endif
    290

ヘッダを送信する。
ファイル送信の前に送信するデータバケットが存在しているケースだ。

ただし、sendfile_nonblocking()から呼ばれている場合、hdtr==NULLだ。
2.4.9のソースをgrepする限り、他の箇所で呼ばれている気配はない。
2.2.27のソースをgrepすると、値が指定されているので、2.4で使わなくなったのか。

    291     if (!hdtr) {

この経路に入る。

    292         hdtr = &no_hdtr;


    252 /* Define a structure to pass in when we have a NULL header value */
    253 static apr_hdtr_t no_hdtr;


    293     }
    294
    295     if (hdtr->numheaders > 0) {

この経路には入らない。

    296         apr_size_t hdrbytes;
    297
    298         /* cork before writing headers */
    299         rv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 1);

TCP_CORKオプションをONにする。
オプションの説明を man tcpからコピペする。

セットされると、 partialフレームを送信しない。
このオプションが解除されると、キューイングされた partial
フレームが送られる。これは sendfile(2) を呼ぶ前にヘッダを前置したり、
スループットを最適化したい場合に便利である。現在の実装では、 TCP_CORK
で出力を抑えることができる時間の上限は 200 ミリ秒である。
この上限に達すると、キューイングされたデータは自動的に送信される。 Linux
2.5.71 以降においてのみ、このオプションを TCP_NODELAY
と同時に用いることができる。
移植性の必要なプログラムではこのオプションを用いるべきではない。

つまり、TCP送信が少し遅延するということだ。sendfileで送信されるデータと このヘッダデータが別個に送信されることを回避できる。

    300         if (rv != APR_SUCCESS) {
    301             return rv;
    302         }
    303
    304         /* Now write the headers */
    305         arv = apr_socket_sendv(sock, hdtr->headers, hdtr->numheaders,
    306                                &hdrbytes);
    307         if (arv != APR_SUCCESS) {
    308             *len = 0;
    309             return errno;
    310         }
    311         nbytes += hdrbytes;
    312
    313         /* If this was a partial write and we aren't doing timeouts,
    314          * return now with the partial byte count; this is a non-blocking
    315          * socket.
    316          */
    317         total_hdrbytes = 0;
    318         for (i = 0; i < hdtr->numheaders; i++) {
    319             total_hdrbytes += hdtr->headers[i].iov_len;
    320         }
    321         if (hdrbytes < total_hdrbytes) {

ヘッダの一部だけ送信して終了した場合には、 sendfileまで行わないで、returnする。

    322             *len = hdrbytes;
    323             return apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);

TCP_CORKをOFFにしている。 ヘッダがキューイングされていた場合には、これで送信される。

    324         }
    325     }
    326

sendfileを実行する ここの処理の流れは、sendfileをwritevに置き換えれば、 apr_socket_sendvと同様


    327     if (sock->options & APR_INCOMPLETE_WRITE) {
    328         sock->options &= ~APR_INCOMPLETE_WRITE;
    329         goto do_select;
    330     }
    331
    332     do {
    333         rv = sendfile(sock->socketdes,    /* socket */
    334                       file->filedes, /* open file descriptor of the file to be sent */
    335                       &off,    /* where in the file to start */
    336                       *len);   /* number of bytes to send */
    337     } while (rv == -1 && errno == EINTR);
    338
    339     while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
    340                       && (sock->timeout > 0)) {
    341 do_select:
    342         arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
    343         if (arv != APR_SUCCESS) {
    344             *len = 0;
    345             return arv;
    346         }
    347         else {
    348             do {
    349                 rv = sendfile(sock->socketdes,    /* socket */
    350                               file->filedes, /* open file descriptor of the file to be sent */
    351                               &off,    /* where in the file to start */
    352                               *len);    /* number of bytes to send */
    353             } while (rv == -1 && errno == EINTR);
    354         }
    355     }
    356
    357     if (rv == -1) {
    358         *len = nbytes;
    359         rv = errno;
    360         apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    361         return rv;
    362     }
    363
    364     nbytes += rv;
    365
    366     if (rv < *len) {
    367         *len = nbytes;
    368         arv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    369         if (rv > 0) {
    370
    371             /* If this was a partial write, return now with the
    372              * partial byte count;  this is a non-blocking socket.
    373              */
    374
    375             if (sock->timeout > 0) {
    376                 sock->options |= APR_INCOMPLETE_WRITE;
    377             }
    378             return arv;
    379         }
    380         else {
    381             /* If the file got smaller mid-request, eventually the offset
    382              * becomes equal to the new file size and the kernel returns 0.
    383              * Make this an error so the caller knows to log something and
    384              * exit.
    385              */
    386             return APR_EOF;
    387         }
    388     }
    389
    390     /* Now write the footers */

フッターを送信する、とある。 ファイル送信の後に送信するデータバケットが存在しているケースだ。 ただし、上で見たとおり、sendfile_nonblocking()から呼ばれたケースでは、hdtrは空なので、 ここの処理も行われない。

    391     if (hdtr->numtrailers > 0) {

sendfile_nonblocking()からでは、この経路には入らない。 処理内容は、ヘッダの時と同様だ。

    392         apr_size_t trbytes;
    393         arv = apr_socket_sendv(sock, hdtr->trailers, hdtr->numtrailers,
    394                                &trbytes);
    395         nbytes += trbytes;
    396         if (arv != APR_SUCCESS) {
    397             *len = nbytes;
    398             rv = errno;
    399             apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    400             return rv;
    401         }
    402     }
    403
    404     apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
    405
    406     (*len) = nbytes;
    407     return rv < 0 ? errno : APR_SUCCESS;
    408 }

これで今回はおしまい。

0 件のコメント:

コメントを投稿