mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Add property to stop the JVM from exiting
spring.main.keep-alive=true will spawn a non-daemon thread which stops if the context is closed Closes gh-37736
This commit is contained in:
parent
6880fb0fc8
commit
fcf77ed65d
@ -376,3 +376,17 @@ Spring Boot ships with the `BufferingApplicationStartup` variant; this implement
|
||||
Applications can ask for the bean of type `BufferingApplicationStartup` in any component.
|
||||
|
||||
Spring Boot can also be configured to expose a {spring-boot-actuator-restapi-docs}/#startup[`startup` endpoint] that provides this information as a JSON document.
|
||||
|
||||
|
||||
|
||||
[[features.spring-application.virtual-threads]]
|
||||
=== Virtual threads
|
||||
If you're running on Java 21 or up, you can enable virtual threads by setting the property configprop:spring.threads.virtual.enabled[] to `true`.
|
||||
|
||||
WARNING: One side effect of virtual threads is that these threads are daemon threads.
|
||||
A JVM will exit if there are no non-daemon threads.
|
||||
This behavior can be a problem when you rely on, e.g. `@Scheduled` beans to keep your application alive.
|
||||
If you use virtual threads, the scheduler thread is a virtual thread and therefore a daemon thread and won't keep the JVM alive.
|
||||
This does not only affect scheduling, but can be the case with other technologies, too!
|
||||
To keep the JVM running in all cases, it is recommended to set the property configprop:spring.main.keep-alive[] to `true`.
|
||||
This ensures that the JVM is kept alive, even if all threads are virtual threads.
|
||||
|
@ -65,6 +65,7 @@ import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
||||
import org.springframework.context.aot.AotApplicationContextInitializer;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
@ -163,6 +164,7 @@ import org.springframework.util.function.ThrowingSupplier;
|
||||
* @author Brian Clozel
|
||||
* @author Ethan Rubinson
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.0.0
|
||||
* @see #run(Class, String[])
|
||||
* @see #run(Class[], String[])
|
||||
@ -240,6 +242,8 @@ public class SpringApplication {
|
||||
|
||||
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
|
||||
|
||||
private boolean keepAlive;
|
||||
|
||||
/**
|
||||
* Create a new {@link SpringApplication} instance. The application context will load
|
||||
* beans from the specified primary sources (see {@link SpringApplication class-level}
|
||||
@ -409,6 +413,11 @@ public class SpringApplication {
|
||||
if (this.lazyInitialization) {
|
||||
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
|
||||
}
|
||||
if (this.keepAlive) {
|
||||
KeepAlive keepAlive = new KeepAlive();
|
||||
keepAlive.start();
|
||||
context.addApplicationListener(keepAlive);
|
||||
}
|
||||
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
|
||||
if (!AotDetector.useGeneratedArtifacts()) {
|
||||
// Load the sources
|
||||
@ -1277,6 +1286,26 @@ public class SpringApplication {
|
||||
return this.applicationStartup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to keep the application alive even if there are no more non-daemon threads.
|
||||
* @return whether to keep the application alive even if there are no more non-daemon
|
||||
* threads
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public boolean isKeepAlive() {
|
||||
return this.keepAlive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to keep the application alive even if there are no more non-daemon threads.
|
||||
* @param keepAlive whether to keep the application alive even if there are no more
|
||||
* non-daemon threads
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public void setKeepAlive(boolean keepAlive) {
|
||||
this.keepAlive = keepAlive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link SpringApplicationShutdownHandlers} instance that can be used to add
|
||||
* or remove handlers that perform actions before the JVM is shutdown.
|
||||
@ -1601,4 +1630,34 @@ public class SpringApplication {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-daemon thread to keep the JVM alive. Reacts to {@link ContextClosedEvent} to
|
||||
* stop itself when the application context is closed.
|
||||
*/
|
||||
private static final class KeepAlive extends Thread implements ApplicationListener<ContextClosedEvent> {
|
||||
|
||||
KeepAlive() {
|
||||
setName("keep-alive");
|
||||
setDaemon(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent event) {
|
||||
interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -387,6 +387,13 @@
|
||||
"type": "org.springframework.boot.cloud.CloudPlatform",
|
||||
"description": "Override the Cloud Platform auto-detection."
|
||||
},
|
||||
{
|
||||
"name": "spring.main.keep-alive",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.boot.SpringApplication",
|
||||
"description": "Whether to keep the application alive even if there are no more non-daemon threads.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "spring.main.lazy-initialization",
|
||||
"type": "java.lang.Boolean",
|
||||
|
@ -158,6 +158,7 @@ import static org.mockito.Mockito.spy;
|
||||
* @author Nguyen Bao Sach
|
||||
* @author Chris Bono
|
||||
* @author Sebastien Deleuze
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SpringApplicationTests {
|
||||
@ -1390,6 +1391,30 @@ class SpringApplicationTests {
|
||||
assertThatNoException().isThrownBy(() -> this.context.getBean(SingleUseAdditionalConfig.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStartDaemonThreadIfKeepAliveIsEnabled() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
this.context = application.run("--spring.main.keep-alive=true");
|
||||
Set<Thread> threads = getCurrentThreads();
|
||||
assertThat(threads).filteredOn((thread) -> thread.getName().equals("keep-alive"))
|
||||
.singleElement()
|
||||
.satisfies((thread) -> assertThat(thread.isDaemon()).isFalse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStopKeepAliveThreadIfContextIsClosed() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
application.setKeepAlive(true);
|
||||
this.context = application.run();
|
||||
Set<Thread> threadsBeforeClose = getCurrentThreads();
|
||||
assertThat(threadsBeforeClose).filteredOn((thread) -> thread.getName().equals("keep-alive")).isNotEmpty();
|
||||
this.context.close();
|
||||
Set<Thread> threadsAfterClose = getCurrentThreads();
|
||||
assertThat(threadsAfterClose).filteredOn((thread) -> thread.getName().equals("keep-alive")).isEmpty();
|
||||
}
|
||||
|
||||
private <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(
|
||||
S state) {
|
||||
return (argument) -> (argument instanceof AvailabilityChangeEvent<?>)
|
||||
@ -1432,6 +1457,10 @@ class SpringApplicationTests {
|
||||
};
|
||||
}
|
||||
|
||||
private Set<Thread> getCurrentThreads() {
|
||||
return Thread.getAllStackTraces().keySet();
|
||||
}
|
||||
|
||||
static class TestEventListener<E extends ApplicationEvent> implements SmartApplicationListener {
|
||||
|
||||
private final Class<E> eventType;
|
||||
|
Loading…
Reference in New Issue
Block a user