[spring-projects/spring-boot]允许停止和重新启动 Web 服务器

2024-05-08 43 views
7

在普通的 Web Spring Boot (3.0.5) 应用程序上,如果这样做context.stop(),然后context.start()a 上的上下文会出现错误:

Exception in thread "main" org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)
        at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356)
        at java.base/java.lang.Iterable.forEach(Iterable.java:75)
        at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155)
        at org.springframework.context.support.DefaultLifecycleProcessor.start(DefaultLifecycleProcessor.java:103)
        at org.springframework.context.support.AbstractApplicationContext.start(AbstractApplicationContext.java:1418)
        at com.example.crac.springwebcrac.SpringWebCracApplication.main(SpringWebCracApplication.java:35)
Caused by: java.lang.IllegalStateException: The host does not contain a Context
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.findContext(TomcatWebServer.java:153)
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:232)
        at org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:44)
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)

要重现创建一个普通的 Web 启动应用程序,SpringWebCracApplication如下所示:

    public static void main(String[] args) throws InterruptedException {
        ConfigurableApplicationContext context = SpringApplication.run(SpringWebCracApplication.class, args);
        context.stop();
        Thread.sleep(5000);
        context.start();
    }

看起来tomcat.destroy(), 调用context.stop(), 销毁了Tomcat.Context, 并且后来在启动时没有重新创建。

请注意,如果我们将 替换Tomcat为 a,Jetty应用程序可以正常工作并在停止/启动后恢复。

回答

3

快速浏览一下实现TomcatWebServerthis.tomcat.destroy()调用可能必须转移到一个只包含调用DisposableBean.destroy()的方法- 这应该允许之后重新调用。根据常规的 Spring 生命周期契约,这是使用 CRaC 项目进行快照恢复所必需的,但也适用于应用程序设置中的自定义停止/启动使用。Lifecycle.stop()stopTomcat()start()stop()

需要明确的是,在 Spring bean 生命周期中,主要目的stop()是在实际调用之前传播预销毁信号destroy(),允许向异步工作线程等发出一些早期关闭信号。在这种情况下,会发生停止和销毁阶段立即进行,因此停止阶段过早的破坏步骤不会被注意到。然而,从概念上讲,停止调用也意味着允许重新启动(即使此时很少使用)。因此,stop()实现需要将其 bean 实例保留在随后可以永久关闭的状态destroy(),而相同的停止后状态也需要让它start()再次从那里恢复。这就是我们此时分离停止/销毁逻辑的指南。

我建议TomcatWebServer立即修改 Boot 3.1,因为这可能与现有的生命周期合同不匹配,并且与其 Jetty 等效项不一致。

5

由于我们可能会在 CRaC 项目的早期演示中使用 Spring Boot 3.1,因此尽快修复此问题也非常好。 @wilkinsona @bclozel 供您考虑,我希望这是一个足够简单的修订。

1

根据修复的复杂性,我会考虑在 3.0.x 甚至 2.7.x 中修复此问题。

4

@markt-asf 在调用后查看状态的一些差异,stop()而不是destory()我注意到StandardServerutilityExecutor调用stop().您能帮助我们理解这意味着什么吗?我想知道这是否会导致 Tomcat 或应用程序代码在一切都应该停止的情况下计划运行。

1

理论上是的,但这将是 Tomcat(或应用程序代码)中的一个错误,当相关组件关闭时,该错误应该关闭这些任务。我快速扫描了 Tomcat 代码,看起来 Tomcat 确实正确关闭了东西。我们最近通过 ServletContext 属性向应用程序公开了实用程序执行器,但它没有被宣传,我怀疑许多应用程序正在使用它(如果是的话,它们需要在 Web 应用程序停止时关闭任务)。

4

谢谢,马克。虽然听起来我们不需要担心执行程序继续运行,但我想知道是否可以停止它stop()(然后在start()需要时再次启动/重新创建)以最大程度地减少发生错误的可能性?

2

这看起来是有可能的。我将在 Tomcat dev@ 列表上启动一个线程来讨论这种可能性。

6

不幸的是,分离stop()destroy()不够。调用stop()会重置上下文,因此需要再次准备好才能重用。目前,此准备工作是由创建 Web 服务器的工厂完成的,而不是由 Web 服务器本身完成的。为了允许停止后重新启动,Web 服务器本身必须能够执行该准备工作。

9

目前,准备使用上下文分为三个受保护的方法:

  • TomcatServletWebServerFactory.prepareContext(Host, ServletContextInitializer[])
  • TomcatServletWebServerFactory.configureContext(Context, ServletContextInitializer[])
  • TomcatServletWebServerFactory.postProcessContext(Context)

我不清楚分裂的原因。prepareContext创建上下文,配置其一些设置,将其添加到 中Host,然后调用其他两个方法。configureContext配置更多上下文设置,并调用TomcatContextCustomizer在工厂注册的任何设置。postProcessContext是一个空方法,可以重写以对上下文进行后处理。据我所知,可以通过覆盖configureContext、调用super,然后进一步配置上下文来实现相同的目的。

7

对此线程有任何反馈吗?删除 stop() 调用中的 this.tomcat.destroy() 是最终解决方案吗?如果可以的话可以提交pr吗?

4

不,这不会是最终的解决方案,但我们还不知道它会是什么。

4

无论如何,我可以帮助研究,所以我可以帮助修复?有后续吗?等

6

@somayaj 感谢您的提议,但这可能是我们自己处理的一个问题,因为它将涉及跨团队协作。

1

我已经将其范围扩大到 Tomcat 之外。我们还可以使用 Jetty(servlet 和响应式)支持停止和重新启动。 Undertow 似乎无法支持停止和重新启动,特别是对于基于 Servlet 的应用程序。它也应该与 Reactor Netty 一起使用,尽管可能没有它所能做到的那么高效。我们可以根据需要在未来进行改进。