How to wait on RestTemplate responses on shutdown?

孤街醉人 提交于 2019-12-06 04:56:14

问题


I'm using spring with RestTemplate to send POST requests to a webserver.

When my application is shut down (eg undeployed from tomcat), the shutdown should be delayed until all pending responses are received (within a timeout).

The restTemplate uses HttpComponentsClientHttpRequestFactory under the hood.

Question: how can I tell spring to delay the shutdown? @PreDestroy could be one possibility, but how can I detect pending requests on the restTemplate?


回答1:


I think there is no out of the box solution as stated in https://github.com/spring-projects/spring-boot/issues/4657

For Tomcat code below should work

@Component
@Scope("singleton")
public class ApplicationContextClosedListener implements ApplicationListener<ContextClosedEvent>, TomcatConnectorCustomizer {

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

    private volatile Connector connector;

    @Value("${timeout}")
    private Integer timeout;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        if (connector != null) {
            shutdownGracefully();
        }
    }

    private void shutdownGracefully() {
        connector.pause();
        Executor executor = connector.getProtocolHandler().getExecutor();

        if (executor instanceof ThreadPoolExecutor) {
            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;

            try {
                threadPoolExecutor.shutdown();
                if (!threadPoolExecutor.awaitTermination(timeout, TimeUnit.SECONDS)) {
                    LOGGER.warn("Shutdown: Tomcat thread pool did not shut down gracefully within specified period. Proceeding with forceful shutdown");
                }
                threadPoolExecutor.shutdownNow();

                LOGGER.info("Shutdown: the executor shutdown completed");
            } catch (InterruptedException ex) {
                LOGGER.error("Shutdown: Interrupt signal received");
                threadPoolExecutor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }
}



回答2:


You can execute all the requests with ExecutorService and then add a @PreDestroy hook to wait for all tasks to be completed within a given timeout. Your service can be like this

@Slf4j
@Component
public class SenderService {
  private static final int TWO_SECONDS = 2;
  private RestTemplate restTemplate;
  private ExecutorService executorService;

  public SenderService() {
    this.restTemplate = new RestTemplate();
    this.executorService = Executors.newFixedThreadPool(1);
  }

  public void sendRequest() throws Exception {
    executorService.submit(() -> {
      ZonedDateTime now = ZonedDateTime.now();
      log.info("Sending request at {} ...", now);
      restTemplate.getForObject("https://httpbin.org/delay/{delay}", Void.class, TWO_SECONDS, now);
      log.info("Response received for request at {}", now);
      return null;
    }).get();
  }

  @PreDestroy
  public void destroy() throws InterruptedException {
    log.info("Shutting down sender service...");
    executorService.shutdown();
    executorService.awaitTermination(3, TimeUnit.SECONDS);
    log.info("Sender service terminated.");
  }
}

The simple way to test this is running the application below and terminating it at some point.

@SpringBootApplication
public class Application {

  public static void main(final String[] args) throws Exception {
    ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
    SenderService senderService = run.getBean(SenderService.class);
    while (true) {
      senderService.sendRequest();
    }
  }
}

If you gracefully shut down the application, you'll see that if a request is sent to delay endpoint, the executorService is going to wait up to 3 seconds for the task to be completed and then terminate the component. executorService.shutdown() initiates a shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

This code is using spring-boot with embedded tomcat, but the same approach could be applied to any spring application context.



来源:https://stackoverflow.com/questions/47570089/how-to-wait-on-resttemplate-responses-on-shutdown

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!