From 9e01b398938e7fd21f601d2e6d9bdc54b9b70164 Mon Sep 17 00:00:00 2001 From: Akhil CH <200716675+Akhil-1527@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:10:28 -0400 Subject: [PATCH 1/2] Call PauseHandler resume from the resume endpoint The resume endpoint only called doResume() when the context was not running. The pause endpoint no longer stops the context, so /actuator/resume never reached the PauseHandler beans after /actuator/pause. Track the paused state and resume when it is set. Signed-off-by: Akhil CH <200716675+Akhil-1527@users.noreply.github.com> --- .../application-context-services.adoc | 2 +- .../context/restart/RestartEndpoint.java | 6 ++- .../restart/RestartIntegrationTests.java | 45 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc b/docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc index d30a642f2..e0ce743bd 100644 --- a/docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc @@ -291,7 +291,7 @@ For a Spring Boot Actuator application, some additional management endpoints are To enabled this endpoint you must set `management.endpoint.env.post.enabled=true` and set a regular expression to limit what properties may be set at `management.endpoint.env.post.valid-keys-regex`. * `/actuator/refresh` to re-load the bootstrap context and refresh the `@RefreshScope` beans. * `/actuator/restart` to close the `ApplicationContext` and restart it (disabled by default). -* `/actuator/pause` and `/actuator/resume` for calling the `Lifecycle` methods (`stop()` and `start()` on the `ApplicationContext`). +* `/actuator/pause` and `/actuator/resume` for calling `pause()` and `resume()` on every `PauseHandler` bean. A handler for Spring Integration `Pausable` components is provided. NOTE: While enabling the `POST` method for `/actuator/env` endpoint can provide flexibility and convenience in managing your application environment variables, it's critical to ensure that the endpoint is secured, monitored and valid keys to update are set to prevent potential security risks. diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java index 661f3eb69..482ca82be 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java @@ -68,6 +68,8 @@ public class RestartEndpoint implements ApplicationListener pauseHandlers = Collections.emptyList(); + private volatile boolean paused; + private long timeout; // @ManagedAttribute @@ -174,6 +176,7 @@ public synchronized void doPause() { for (PauseHandler handler : this.pauseHandlers) { handler.pause(); } + this.paused = true; } // @ManagedOperation @@ -182,6 +185,7 @@ public synchronized void doResume() { PauseHandler handler = this.pauseHandlers.get(i); handler.resume(); } + this.paused = false; } private void overrideClassLoaderForRestart() { @@ -235,7 +239,7 @@ public class ResumeEndpoint { @WriteOperation public Boolean resume() { - if (!isRunning()) { + if (RestartEndpoint.this.paused) { doResume(); return true; } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java index c6ec85b47..57c168854 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java @@ -22,6 +22,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.BDDAssertions.then; @@ -70,10 +71,54 @@ public void testRestartTwice() { */ } + @Test + public void testResumeAfterPause() { + this.context = SpringApplication.run(PauseHandlerConfiguration.class, + "--management.endpoint.restart.enabled=true", "--server.port=0", + "--management.endpoints.web.exposure.include=restart,pause,resume"); + + RestartEndpoint endpoint = this.context.getBean(RestartEndpoint.class); + TestPauseHandler handler = this.context.getBean(TestPauseHandler.class); + + then(endpoint.getResumeEndpoint().resume()).isFalse(); + then(endpoint.getPauseEndpoint().pause()).isTrue(); + then(handler.paused).isTrue(); + then(endpoint.getResumeEndpoint().resume()).isTrue(); + then(handler.paused).isFalse(); + then(endpoint.getResumeEndpoint().resume()).isFalse(); + } + @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration protected static class TestConfiguration { } + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + protected static class PauseHandlerConfiguration { + + @Bean + TestPauseHandler testPauseHandler() { + return new TestPauseHandler(); + } + + } + + static class TestPauseHandler implements PauseHandler { + + boolean paused; + + @Override + public void pause() { + this.paused = true; + } + + @Override + public void resume() { + this.paused = false; + } + + } + } From 064b056b38fe46cd05c82fbdcfbd7d98a64fbcd4 Mon Sep 17 00:00:00 2001 From: Akhil CH <200716675+Akhil-1527@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:40:38 -0400 Subject: [PATCH 2/2] Reset pause state when the context restarts After /actuator/pause then /actuator/restart the endpoint kept paused = true and still held the PauseHandler beans of the closed context. Clear both when the context closes and collect the handlers again from the new context. Signed-off-by: Akhil CH <200716675+Akhil-1527@users.noreply.github.com> --- .../context/restart/RestartEndpoint.java | 14 ++++++++++--- .../restart/RestartIntegrationTests.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java b/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java index 482ca82be..6ee73040a 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/context/restart/RestartEndpoint.java @@ -95,12 +95,16 @@ public void onApplicationEvent(ContextRefreshedWithApplicationEvent input) { this.args = this.event.getArgs(); this.application = this.event.getSpringApplication(); this.application.addInitializers(new PostProcessorInitializer()); - this.pauseHandlers = this.context.getBeanProvider(PauseHandler.class) - .orderedStream() - .collect(Collectors.toList()); + collectPauseHandlers(); } } + private void collectPauseHandlers() { + this.pauseHandlers = this.context.getBeanProvider(PauseHandler.class) + .orderedStream() + .collect(Collectors.toList()); + } + @WriteOperation public Object restart() { Thread thread = new Thread(this::safeRestart); @@ -142,10 +146,14 @@ public synchronized ConfigurableApplicationContext doRestart() { } this.application.setEnvironment(this.context.getEnvironment()); close(); + // The handlers and the paused state belong to the context that was closed + this.pauseHandlers = Collections.emptyList(); + this.paused = false; // If running in a webapp then the context classloader is probably going to // die so we need to revert to a safe place before starting again overrideClassLoaderForRestart(); this.context = this.application.run(this.args); + collectPauseHandlers(); } return this.context; } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java index 57c168854..5000b0fb3 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java @@ -88,6 +88,27 @@ public void testResumeAfterPause() { then(endpoint.getResumeEndpoint().resume()).isFalse(); } + @Test + public void testRestartAfterPause() { + this.context = SpringApplication.run(PauseHandlerConfiguration.class, + "--management.endpoint.restart.enabled=true", "--server.port=0", + "--management.endpoints.web.exposure.include=restart,pause,resume"); + + RestartEndpoint endpoint = this.context.getBean(RestartEndpoint.class); + TestPauseHandler closed = this.context.getBean(TestPauseHandler.class); + + then(endpoint.getPauseEndpoint().pause()).isTrue(); + this.context = endpoint.doRestart(); + TestPauseHandler handler = this.context.getBean(TestPauseHandler.class); + then(handler).isNotSameAs(closed); + + then(endpoint.getResumeEndpoint().resume()).isFalse(); + then(endpoint.getPauseEndpoint().pause()).isTrue(); + then(handler.paused).isTrue(); + then(endpoint.getResumeEndpoint().resume()).isTrue(); + then(handler.paused).isFalse(); + } + @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration protected static class TestConfiguration {