CLOVER🍀

That was when it all began.

Codehaus Cargo Maven 3 PluginとApache Tomcat 10.1でcargo:runを使った時に、停止が遅いのをなんとかしたい

これは、なにをしたくて書いたもの?

前に、Codehaus Cargo Maven 3 Pluginを使ってApache Tomcatをダウンロードして、Webアプリケーションをデプロイ、
実行するといったことをやってみました。

Codehaus Cargo Maven 3 Pluginを使って、Apache Tomcatをダウンロードしてきてデプロイする - CLOVER🍀

これはこれで便利で、Apache Tomcatを使う時はこれでいいのではないかなと思っていたのですが、ひとつだけ難点が
ありまして。

cargo:runした後にCtrl-cで停止しようとすると、やたら遅かったんですよね。今回はこれをなんとかしたいという話です。

動かしながら、どういう話か書いていきましょう。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.6 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-55-generic", arch: "amd64", family: "unix"

サンプルアプリケーションを作成する

まずは動かえないと話が始まらないので、簡単なWebアプリケーションを作成します。

pom.xmlの抜粋。

    ...
    <packaging>war</packaging>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>1.10.18</version>
                <configuration>
                    <container>
                        <containerId>tomcat10x</containerId>
                        <zipUrlInstaller>
                            <url>https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.39/bin/apache-tomcat-10.1.39.tar.gz</url>
                            <!-- <url>https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/10.1.39/tomcat-10.1.39.zip</url> -->
                        </zipUrlInstaller>
                    </container>
                </configuration>
            </plugin>
        </plugins>
    </build>

簡単なServletを作成。

src/main/java/org/littlewings/tomcat/cargo/HelloServlet.java

package org.littlewings.tomcat.cargo;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().println("Hello Tomcat!!");
    }
}

Codehaus Cargo Maven 3 Pluginのcargo:runゴールを実行。

$ mvn package cargo:run

Apache Tomcatが起動します。

[INFO] 3月 15, 2025 9:06:31 午後 org.apache.coyote.AbstractProtocol start
[INFO] 情報: プロトコルハンドラー ["http-nio-8080"] を開始しました。
[INFO] 3月 15, 2025 9:06:31 午後 org.apache.catalina.startup.Catalina start
[INFO] 情報: サーバーの起動 [307] ミリ秒
[INFO] Tomcat 10.1.39 started on port [8080]
[INFO] Press Ctrl-C to stop the container...

動作確認。

$ curl localhost:8080/hello
Hello Tomcat!!

Press Ctrl-C to stop the container...と出ていたので、Ctrl-cで止めてみます。

^C[INFO] Tomcat 10.1.39 is stopping...
[WARNING] Error reading process stream: java.io.IOException: Stream closed
[INFO] 3月 15, 2025 9:06:55 午後 org.apache.catalina.startup.Catalina stopServer
[INFO] 重大: [localhost:8205] に接続できませんでした (base ポート [8205]、offset [0])。Tomcatが実行中でない可能性があります。
[INFO] 3月 15, 2025 9:06:55 午後 org.apache.catalina.startup.Catalina stopServer
[INFO] 重大: Catalinaの停止エラー
[INFO] java.net.ConnectException: 接続を拒否されました
[INFO]  at java.base/sun.nio.ch.Net.connect0(Native Method)
[INFO]  at java.base/sun.nio.ch.Net.connect(Net.java:589)
[INFO]  at java.base/sun.nio.ch.Net.connect(Net.java:578)
[INFO]  at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:583)
[INFO]  at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
[INFO]  at java.base/java.net.Socket.connect(Socket.java:751)
[INFO]  at java.base/java.net.Socket.connect(Socket.java:686)
[INFO]  at java.base/java.net.Socket.<init>(Socket.java:555)
[INFO]  at java.base/java.net.Socket.<init>(Socket.java:324)
[INFO]  at org.apache.catalina.startup.Catalina.stopServer(Catalina.java:657)
[INFO]  at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[INFO]  at java.base/java.lang.reflect.Method.invoke(Method.java:580)
[INFO]  at org.apache.catalina.startup.Bootstrap.stopServer(Bootstrap.java:393)
[INFO]  at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:478)
[INFO]

すると例外が投げられるのですが、コンソール上はそのまま待つことになります。

そして、5秒くらい待っていると停止します。

[INFO] Tomcat 10.1.39 is stopped

これがけっこうストレスなのですが、この時になにを待っているのかスレッドダンプを取ってみます。

$ jcmd $(jcmd -l | grep cargo:run | perl -wp -e 's!^(\d+) .+!$1!') Thread.print

すると、コンテナ(Apache Tomcat)の停止を待っているようです。

"main" #1 [59908] prio=5 os_prio=0 cpu=2116.34ms elapsed=9.43s tid=0x000076c0c4019890 nid=59908 waiting on condition  [0x000076c0c8bfd000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep0(java.base@21.0.6/Native Method)
        at java.lang.Thread.sleep(java.base@21.0.6/Thread.java:509)
        at org.codehaus.cargo.container.spi.util.ContainerUtils.waitTillContainerIsStopped(ContainerUtils.java:87)
        at org.codehaus.cargo.maven3.ContainerRunMojo.doExecute(ContainerRunMojo.java:144)
        at org.codehaus.cargo.maven3.AbstractCargoMojo.execute(AbstractCargoMojo.java:466)
        at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:126)

なんですけど、このあたりを見ているとどうも先にApache Tomcatの方が止まっていそうですよね…。

[WARNING] Error reading process stream: java.io.IOException: Stream closed
[INFO] 3月 15, 2025 9:06:55 午後 org.apache.catalina.startup.Catalina stopServer
[INFO] 重大: [localhost:8205] に接続できませんでした (base ポート [8205]、offset [0])。Tomcatが実行中でない可能性があります。
[INFO] 3月 15, 2025 9:06:55 午後 org.apache.catalina.startup.Catalina stopServer
[INFO] 重大: Catalinaの停止エラー
[INFO] java.net.ConnectException: 接続を拒否されました
[INFO]  at java.base/sun.nio.ch.Net.connect0(Native Method)

ちなみに、このあたりを設定してタイムアウトを短くしても動作は変わりませんでした…。

Codehaus Cargo - Container Timeout

というわけで、これをなんとかしたいというのが今回のお題です。

Codehaus Cargo Maven 3 Pluginはなにを待っているのか?

スレッドダンプで取得できた箇所などをヒントに見ていってみましょう。

cargo:runゴールを実行すると、Runtime#addShutdownHookで終了処理が登録されます。

https://github.com/codehaus-cargo/cargo/blob/codehaus-cargo-1.10.18/extensions/maven3/plugin/src/main/java/org/codehaus/cargo/maven3/ContainerRunMojo.java#L75-L120

それはそれとして、スレッドダンプで表示されているのはここで待っている部分ですね。

https://github.com/codehaus-cargo/cargo/blob/codehaus-cargo-1.10.18/extensions/maven3/plugin/src/main/java/org/codehaus/cargo/maven3/ContainerRunMojo.java#L144

スタックトレースの様子からすると、Apache Tomcat側が先に止まっているのにそれに気づかずに待ち続けているみたいですね。

https://github.com/codehaus-cargo/cargo/blob/codehaus-cargo-1.10.18/core/api/container/src/main/java/org/codehaus/cargo/container/spi/util/ContainerUtils.java#L83-L93

このLocalConnectorというものですが、Apache Tomcatの場合はこちらがベースになっています。

https://github.com/codehaus-cargo/cargo/blob/master/core/containers/tomcat/src/main/java/org/codehaus/cargo/container/tomcat/internal/AbstractCatalinaInstalledLocalContainer.java

この親をたどっていくと、AbstractLocalContainerのこの箇所にたどり着きます。

            // CARGO-712: If timeout is 0, don't wait at all
            if (getTimeout() != 0)
            {
                // Wait until the container is fully stopped
                waitForCompletion(false);
            }

https://github.com/codehaus-cargo/cargo/blob/codehaus-cargo-1.10.18/core/api/container/src/main/java/org/codehaus/cargo/container/spi/AbstractLocalContainer.java#L325-L330

これを見ると、タイムアウト設定を0にすれば待たなくなりそうですね。

CARGO-712はどういうものだったのだろう?と思って見てみたのですが、どうやらこれは起動に待機しないように
意図したものだったようです。

Created: (CARGO-712) Setting timeout to 0 should tell cargo not to wait for the container to start

タイムアウトを設定してみる

というわけで、試しにタイムアウトを0に設定してみます。

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>1.10.18</version>
                <configuration>
                    <container>
                        <containerId>tomcat10x</containerId>
                        <zipUrlInstaller>
                            <url>https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.39/bin/apache-tomcat-10.1.39.tar.gz</url>
                            <!-- <url>https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/10.1.39/tomcat-10.1.39.zip</url> -->
                        </zipUrlInstaller>
                        <timeout>0</timeout>
                    </container>
                </configuration>
            </plugin>

cargo:runで起動後にCtrl-cで停止すると、一瞬で止まるようになりました(笑)。

^C[INFO] Tomcat 10.1.39 is stopping...
[INFO] Tomcat 10.1.39 is stopped
[WARNING] Error reading process stream: java.io.IOException: Stream closed

0ではなく極端に短い値にするとうまく起動できなくなるのですが、それならと2秒くらいにしてみるとCtrl-cでの停止時は
やっぱり5秒くらい待つことになります。

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>1.10.18</version>
                <configuration>
                    <container>
                        <containerId>tomcat10x</containerId>
                        <zipUrlInstaller>
                            <url>https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.39/bin/apache-tomcat-10.1.39.tar.gz</url>
                            <!-- <url>https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/10.1.39/tomcat-10.1.39.zip</url> -->
                        </zipUrlInstaller>
                        <timeout>2000</timeout>
                    </container>
                </configuration>
            </plugin>

また、けっこう豪快な(?)設定なのでexecutions内に絞った設定にした方がいいのかなと思ったりもしたのですが、
これも効果がありませんでした…。

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven3-plugin</artifactId>
                <version>1.10.18</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>run</goal>
                            <goal>start</goal>
                            <goal>stop</goal>
                        </goals>
                        <configuration>
                            <container>
                                <timeout>0</timeout>
                            </container>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <container>
                        <containerId>tomcat10x</containerId>
                        <zipUrlInstaller>
                            <url>https://archive.apache.org/dist/tomcat/tomcat-10/v10.1.39/bin/apache-tomcat-10.1.39.tar.gz</url>
                            <!-- <url>https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/10.1.39/tomcat-10.1.39.zip</url> -->
                        </zipUrlInstaller>
                    </container>
                </configuration>
            </plugin>

というわけで、全体に設定するしかなさそうです…。

おわりに

Codehaus Cargo Maven 3 PluginとApache Tomcat 10.1でcargo:runを使った時に、停止が遅いのをなんとかしたいということで
いろいろ試してみました。

結果としてはなんとかなる方法はあったのですが、利用する状況によってはちょっと微妙な設定だなとは思います。

まあ、自分が使う分にはこれでもいいかなと…。