Embedded tomcat 运行 as windows 服务需要很长时间才能停止服务

Embedded tomcat running as windows service takes long time to stop the service

我有一个使用嵌入式 tomcat(9.0.44) 的可执行 jar 文件。及其 运行 作为 windows 服务(名为“MyApp 测试服务”)与 apache prunsrv 实用程序。 但是当我尝试停止服务时,需要一些时间(超过一分钟)才能停止服务。但是启动服务非常快。 我可以确认 tomcat 的 stop() 方法很快完成。我怀疑 prunsrv 中还有其他东西等待并需要时间来停止服务。请帮助了解这是怎么回事以及如何解决这个问题(执行后立即停止服务tomcat.stop()

Tomcat嵌入式服务器 .java

public class TomcatEmbeddedServer {
    private int port;
    private Tomcat tomcat;
    private Context context;

    private static final Logger LOGGER = LoggerFactory.getLogger(TomcatEmbeddedServer.class);

    public TomcatEmbeddedServer(int port, String contextName, String tomcatPath, String webAppPath) {
        System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
        System.setProperty("org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "true");

        this.port = port;

        LOGGER.info("Port: {}", port);
        LOGGER.info("Context: {}", contextName);
        LOGGER.info("Tomcat Path: {}", tomcatPath);
        LOGGER.info("Webapp Path: {}", webAppPath);

        // Prepare tomcat server
        this.tomcat = new Tomcat();
        this.tomcat.setBaseDir(tomcatPath);

        // Set up the context
        this.context = this.tomcat.addWebapp(contextName, webAppPath);
        this.context.setParentClassLoader(Thread.currentThread().getContextClassLoader());
    }

    public void start() throws LifecycleException {

        Connector connector = this.tomcat.getConnector();
        connector.setEncodedSolidusHandling(EncodedSolidusHandling.DECODE.getValue());
        connector.setPort(this.port);

        LOGGER.info("Starting tomcat server ...{}", this.tomcat);
        this.tomcat.start();
        this.tomcat.getServer().await();
    }

    public void stop() throws LifecycleException {
        this.tomcat.stop();
    }
}

TestEmbeddedServer.java

public class TestEmbeddedServer {

    private static TomcatEmbeddedServer tomcat;
    private static Logger log = LoggerFactory.getLogger(TestEmbeddedServer.class);

    public static void main(String[] args) {
        String tomcatDir = "D:/testserver/tomcat";
        String webAppDir = "D:/testserver/mysampleapp";
        String context = "/sampleapp";
        int port = 8090;

        try
        {
            tomcat = new TomcatEmbeddedServer(port, context, tomcatDir, webAppDir);
            tomcat.start();
        } catch (Exception e) {
            log.error("Failed to start tomcat server." , e);
        }
    }

    public static void stop(String[] args) {
        try {
            tomcat.stop();
        } catch (LifecycleException e) {
            log.error("Failed to stop tomcat.", e);
        }
    }
}

下面是我得到的prunservstartup/shutdown日志

[2021-06-02 10:42:36] [debug] ( prunsrv.c:1729) [85696] Commons Daemon procrun log initialized
[2021-06-02 10:42:36] [info]  ( prunsrv.c:1733) [85696] Commons Daemon procrun (1.1.0.0 64-bit) started
[2021-06-02 10:42:36] [info]  ( prunsrv.c:1643) [85696] Running 'MyApp Test Service' Service...
[2021-06-02 10:42:36] [debug] ( prunsrv.c:1417) [85736] Inside ServiceMain...
[2021-06-02 10:42:36] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 2, dwWin32ExitCode = 0, dwWaitHint = 3000, dwServiceSpecificExitCode = 0
[2021-06-02 10:42:36] [info]  ( prunsrv.c:1175) [85736] Starting service...
[2021-06-02 10:42:36] [debug] ( javajni.c:236 ) [85736] loading jvm 'C:\Program Files\Java\bin\server\jvm.dll'
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[0] -Dfile.encoding=UTF8
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[1] --add-opens=java.base/java.lang=ALL-UNNAMED
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[2] --add-opens=java.base/java.io=ALL-UNNAMED
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[3] --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[4] --illegal-access=permit
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[5] exit
[2021-06-02 10:42:36] [debug] ( javajni.c:753 ) [85744] Jvm Option[6] abort
[2021-06-02 10:42:39] [debug] ( javajni.c:990 ) [85744] Java Worker thread started com/samples/myapp/TestEmbeddedServer:main
[2021-06-02 10:42:40] [debug] ( prunsrv.c:1235) [85736] Java started com/samples/myapp/TestEmbeddedServer
[2021-06-02 10:42:40] [info]  ( prunsrv.c:1333) [85736] Service started in 4102 ms.
[2021-06-02 10:42:40] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 4, dwWin32ExitCode = 0, dwWaitHint = 0, dwServiceSpecificExitCode = 0
[2021-06-02 10:42:40] [debug] ( prunsrv.c:1572) [85736] Waiting for worker to finish...
[2021-06-02 10:43:38] [debug] ( prunsrv.c:885 ) [85696] reportServiceStatusE: dwCurrentState = 3, dwWin32ExitCode = 0, dwWaitHint = 3000, dwServiceSpecificExitCode = 0
[2021-06-02 10:43:39] [info]  ( prunsrv.c:984 ) [85296] Stopping service...
[2021-06-02 10:43:40] [debug] ( javajni.c:990 ) [85488] Java Worker thread started com/samples/myapp/TestEmbeddedServer:stop
[2021-06-02 10:43:41] [debug] ( prunsrv.c:1032) [85296] Waiting for Java JNI stop worker to finish...
[2021-06-02 10:43:48] [debug] ( javajni.c:1013) [85744] Java Worker thread finished com/samples/myapp/TestEmbeddedServer:main with status = 0
[2021-06-02 10:43:48] [debug] ( javajni.c:1013) [85488] Java Worker thread finished com/samples/myapp/TestEmbeddedServer:stop with status = 0
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1034) [85296] Java JNI stop worker finished.
[2021-06-02 10:43:48] [debug] ( prunsrv.c:885 ) [85296] reportServiceStatusE: dwCurrentState = 3, dwWin32ExitCode = 0, dwWaitHint = 5000, dwServiceSpecificExitCode = 0
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1141) [85296] Waiting for worker to die naturally...
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1152) [85296] Worker finished gracefully in 0 ms.
[2021-06-02 10:43:48] [info]  ( prunsrv.c:1162) [85296] Service stop thread completed.
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1577) [85736] Worker finished.
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1586) [85736] Waiting for ShutdownEvent
[2021-06-02 10:43:48] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 3, dwWin32ExitCode = 0, dwWaitHint = 60000, dwServiceSpecificExitCode = 0
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1589) [85736] ShutdownEvent signaled
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1594) [85736] Waiting 1 minute for all threads to exit
[2021-06-02 10:43:48] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 3, dwWin32ExitCode = 0, dwWaitHint = 60000, dwServiceSpecificExitCode = 0
[2021-06-02 10:44:48] [debug] ( prunsrv.c:1607) [85736] JVM destroyed.
[2021-06-02 10:44:48] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 1, dwWin32ExitCode = 0, dwWaitHint = 0, dwServiceSpecificExitCode = 0
[2021-06-02 10:44:48] [info]  ( prunsrv.c:1645) [85696] Run service finished.
[2021-06-02 10:44:48] [info]  ( prunsrv.c:1814) [85696] Commons Daemon procrun finished

从这些日志中可以看出,在关闭信号后,有 1 分钟的延迟来销毁 JVM。我想这就是它挂的地方。

[2021-06-02 10:43:48] [debug] ( prunsrv.c:1589) [85736] ShutdownEvent signaled
[2021-06-02 10:43:48] [debug] ( prunsrv.c:1594) [85736] Waiting 1 minute for all threads to exit
[2021-06-02 10:43:48] [debug] ( prunsrv.c:885 ) [85736] reportServiceStatusE: dwCurrentState = 3, dwWin32ExitCode = 0, dwWaitHint = 60000, dwServiceSpecificExitCode = 0
[2021-06-02 10:44:48] [debug] ( prunsrv.c:1607) [85736] JVM destroyed.

在 prunmngr UI 中,进度指示器已关闭,但开始按钮未启用。请参阅下面的屏幕截图。

编辑:通过改变 Tomcat

的版本得到结果
Tomcat 使用的版本 停止所用时间
Apache Tomcat/8.5.66 ~9秒
Apache Tomcat/9.0.1 ~ 9 秒
Apache Tomcat/9.0.10 ~ 9 秒
Apache Tomcat/9.0.13 ~ 9 秒
Apache Tomcat/9.0.14 ~ 1分3秒
Apache Tomcat/9.0.16 ~ 1分3秒
Apache Tomcat/9.0.20 ~ 1分3秒
Apache Tomcat/9.0.30 ~ 1分3秒
Apache Tomcat/9.0.40 ~ 1分3秒
Apache Tomcat/9.0.44 ~ 1分3秒
Apache Tomcat/9.0.46 ~ 1分3秒
Apache Tomcat/10.0.6 ~ 1分3秒

自 Tomcat 版本 9.0.14 以来引入了实用程序执行器:

Add a scheduled executor to the Server, which can be used to process periodic utility tasks. The utility threads are non daemon by default. (remm)

它的线程有意为非守护进程,因此服务器 stop() 不会关闭 JVM。要完全停止服务器,您必须使用 destroy():

    public void stop() throws LifecycleException {
        this.tomcat.stop();
        this.tomcat.destroy();
    }