Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ public class RestartEndpoint implements ApplicationListener<ContextRefreshedWith

private List<PauseHandler> pauseHandlers = Collections.emptyList();

private volatile boolean paused;

private long timeout;

// @ManagedAttribute
Expand All @@ -93,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);
Expand Down Expand Up @@ -140,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;
}
Expand Down Expand Up @@ -174,6 +184,7 @@ public synchronized void doPause() {
for (PauseHandler handler : this.pauseHandlers) {
handler.pause();
}
this.paused = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only set this to true if !this.pauseHandlers.isEmpty(). Otherwise /actuator/resume will return true when nothing was paused

}

// @ManagedOperation
Expand All @@ -182,6 +193,7 @@ public synchronized void doResume() {
PauseHandler handler = this.pauseHandlers.get(i);
handler.resume();
}
this.paused = false;
}

private void overrideClassLoaderForRestart() {
Expand Down Expand Up @@ -235,7 +247,7 @@ public class ResumeEndpoint {

@WriteOperation
public Boolean resume() {
if (!isRunning()) {
if (RestartEndpoint.this.paused) {
doResume();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,10 +71,75 @@ 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();
}

@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 {

}

@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;
}

}

}
Loading