Akira's Tech Notes

Java/JVM | GNU/Linux | Emacs/Lisp | 知的好奇心駆動

header-icon
ネイティブでない日本語で思い付くことや気になることをダラダラ書く、体裁とかは気にしない。読みづらいと感じた時に随時更新する。

[調査]JBossAS7のソケット操作タイムアウト値について

JBossAS の起動オプション org.apache.coyote.http11.DEFAULT_CONNECTION_TIMEOUT につい て誤解があったので、メモしておきます。

まず、ドキュメントの説明。デフォルトタイムアウト値が60秒ですね。

org.apache.coyote.http11.DEFAULT_CONNECTION_TIMEOUT
Default socket timeout. The default value is 60000 ms.

説明だけでよく分からない、最初はサーバサイトワーカスレッドの処理時間タイムアウト値 と理解しました。

テストアプリでタイムアウト時の動作を確認する。

+----------+            +----------------+
| SoapUI   +------------+  テストアプリ  |
+----------+            |                |
                        +----------------+
                        |  JBossAS 7.x   |
                        +----------------+

SoapUIのリードタイムアウトを120秒に変更する、テストアプリに Thread.sleep(70 * 1000) コードを仕込んで処理時間を長くする。これでタイムアウトになると考えたのですが、結果的に タイムアウトせず、正常なレスポンスでした。これで、 DEFAULT_CONNECTION_TIMEOUT はワー カスレッドのタイムアウト値ではないことが分かりました。

説明文にソケットのタイムアウト値であることが書かれたので telnet でソケット通信の各 stepの処理時間をあえて長くして動作状況を観測してみました。

  1. telnetでコネクションを貼る
  2. ヘッダを途中まで送信する
  3. 60秒待つこと
$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
POST http://localhost:8080/testapp/HelloWorld HTTP/1.1
Connection: close
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "sayHello"
Content-Length: 318
Host: localhost:8080
★ここで60秒を待つ
Connection closed by foreign host.

step3のところで60秒後、サーバからfinパケットが送出されてコネクションが切れた。 DEFAULT_CONNECTION_TIMEOUT パラメータの意味が少し掴みました。

少し掘り下げて見たいので、コードリーディングして実装の概要を以下に紹介します。

1 DEFAULT_CONNECTION_TIMEOUTオプションの役割

JBossASのJava起動オプションで指定する -Dorg.apache.coyote.http11.DEFAULT_CONNECTION_TIMEOUT=xxxxx オプションの値は最終的に ソケット操作のブロックタイムアウト値 java.net.ServerSocket.setSoTimeout(xx) に反映さ れる。

サーバ側で以下のソケット操作でブロックされる時間が指定時間より超えるとタイムアウトさせ る機能です。

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

例えば、HTTPヘッダ受信途中クライアント側がストール状態になり、 SocketInputStream.read() 処理がずっと待ち続けるには行けないので(FDのリソース枯渇に なるかも知らない)、タイムアウトさせる必要があります。

2 JBossASの実装

org.apache.coyote.http11.Constants にデフォルト値が60秒で定義されている。

public final class Constants {
    ...............
    //★ここでJavaオプションから値を参照している
    public static final int DEFAULT_CONNECTION_TIMEOUT =
            Integer.valueOf(System.getProperty("org.apache.coyote.http11.DEFAULT_CONNECTION_TIMEOUT", "60000")).intValue();
    .............

この値は以下の流れで、 JIoEndpoint クラスの soTimeout 属性に反映される。

1.org.jboss.as.web.WebConnectorService.start()
2.  org.apache.catalina.connector.Connectorのコンストラクタ処理
3.    apache.coyote.http11.Http11Protocolのコンストラクタ処理
4.      org.apache.tomcat.util.net.JIoEndpoint.setSoTimeout(xx)

acceptor スレッドで新規コネクションをacceptしワーカスレッドに渡した後にワーカスレッ ドの run 関数内 java.net.ServerSocket.setSoTimeout(xx) 関数を呼び出し、値を設定す る。具体的な流れは以下となります。

1. org.apache.tomcat.util.net.JIoEndpoint.Acceptor.run()
2.   org.apache.tomcat.util.net.JIoEndpoint.processSocket(socket)
3.     ★ここで分岐がある、別途検証する
4.     org.apache.tomcat.util.net.JIoEndpoint.Workerが起動される
5.       org.apache.tomcat.util.net.JIoEndpoint.Worker.run()
6.         org.apache.tomcat.util.net.JIoEndpoint.setSocketOptions(socket)
7.           socket.setSoTimeout(soTimeout);

3 タイムアウト時の挙動

3.1 HTTPヘッダー受信途中タイムアウトの場合

Cookiesが大きいの場合、HTTPヘッダー電文が複数パケットに分割する可能性が高い。 ネットワーク障害でHTTPヘッダー受信途中でタイムアウトが起きた場合サーバからFINパケット よりコネクションが切断される。

以下はHTTPヘッダー受信処理のコードの抜粋です。タイムアウトが起きた場合★3の部分が実 行される。 エラーログを吐かずに処理が終了してしまう。

org.apache.coyote.http11.Http11Processor

/**
 * Process pipelined HTTP requests on the specified socket.
 *
 * @param socket Socket from which the HTTP requests will be read
 *               and the HTTP responses will be written.
 *
 * @throws IOException error during an I/O operation
 */
public SocketState process(Socket socket)
    throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

    //★★★0 いろいろ省略する

    while (!error && keepAlive && !event) {

        // Parsing the request header
        try {
            if (!disableUploadTimeout && keptAlive) {
                if (keepAliveTimeout > 0) {
                    socket.setSoTimeout(keepAliveTimeout);
                }
                else if (soTimeout > 0) {
                    socket.setSoTimeout(soTimeout);
                }
            }

            //★★★1 HTTPヘッダー1行目の受信処理
            inputBuffer.parseRequestLine();
            request.setStartTime(System.currentTimeMillis());
            keptAlive = true;
            if (!disableUploadTimeout) {
                socket.setSoTimeout(timeout);
            }

            //★★★2 HTTPヘッダーの受信処理
            inputBuffer.parseHeaders();
        } catch (IOException e) {

            //★★★3 ネットワークIOエラー時、処理を止める
            error = true;
            break;
        } catch (Throwable t) {
            CoyoteLogger.HTTP_LOGGER.errorParsingHeader(t);
            // 400 - Bad Request
            response.setStatus(400);
            error = true;
        }

★3の部分にエラー出力処理を追加しらた、タイムアウト時下記のエラートレースが出力され た。

19:23:49,020 ERROR [stderr] (http-/0.0.0.0:8080-1) java.net.SocketTimeoutException: Read timed out
19:23:49,020 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at java.net.SocketInputStream.socketRead0(Native Method)
19:23:49,021 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at java.net.SocketInputStream.read(SocketInputStream.java:152)
19:23:49,021 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at java.net.SocketInputStream.read(SocketInputStream.java:122)
19:23:49,027 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:721)
19:23:49,028 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.coyote.http11.InternalInputBuffer.parseHeader(InternalInputBuffer.java:660)
19:23:49,028 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.coyote.http11.InternalInputBuffer.parseHeaders(InternalInputBuffer.java:516)
19:23:49,029 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:828)
19:23:49,032 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:653)
19:23:49,033 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:920)
19:23:49,034 ERROR [stderr] (http-/0.0.0.0:8080-1) 	at java.lang.Thread.run(Thread.java:745)

3.2 リクエスト本文受信途中タイムアウトの場合

以下、HTTP本文読み取り処理(HTTP POST)でブロックタイムアウトが起きた時のサーバログです。

Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method) [rt.jar:1.7.0_65]
	at java.net.SocketInputStream.read(SocketInputStream.java:152) [rt.jar:1.7.0_65]
	at java.net.SocketInputStream.read(SocketInputStream.java:122) [rt.jar:1.7.0_65]
	at org.apache.coyote.http11.InternalInputBuffer.fill(InternalInputBuffer.java:737) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	at org.apache.coyote.http11.InternalInputBuffer$InputStreamInputBuffer.doRead(InternalInputBuffer.java:767) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	at org.apache.coyote.http11.filters.IdentityInputFilter.doRead(IdentityInputFilter.java:116) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	at org.apache.coyote.http11.InternalInputBuffer.doRead(InternalInputBuffer.java:697) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	at org.apache.coyote.Request.doRead(Request.java:438) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	at org.apache.catalina.connector.InputBuffer.realReadBytes(InputBuffer.java:341) [jbossweb-7.2.2.Final-redhat-1.jar:7.2.2.Final-redhat-1]
	... 47 more

4 注意書き

org.apache.coyote.http11.DEFAULT_CONNECTION_TIMEOUT が次のように誤解されることもあ るので、要注意です。

ワーカスレッドの実行時間のタイムアウト値

あくまでもソケットのブロックタイムアウト値なので、アプリのロジック処理時と関係ないで す。主にHTTPヘッダとHTTP本文の読み取り処理のブロック時間を監視するために使われる。

Comments