From 8db33e8ce2f4be4146d7b7989a8ef1e40448f9e7 Mon Sep 17 00:00:00 2001 From: Kai Moritz Date: Wed, 6 Mar 2024 08:26:03 +0100 Subject: [PATCH] fix: The shutdown of the application was blocked * The auto-configured bean `applicationTaskExecutor` must not block, while it is shutting down, because otherwise, it infinitly waits for the completion, of the channel-tasks, which are stopped in a _later_ phase of the "smart" lifecycle. * The bean is destroyed first, becaus it is is associated with the lowest lifecyle-phase (``Integer.MAX_VALUE``), which apparently cannot be overruled by `@DependsOn` (although suggested by the spring- documentation) * Joining the channel-tasks was blocking infinitly, because the tasks were waiting for the kafka-consumers to be closed, what only happens _after_ the joining completes. --- .../kafka/KafkaServicesApplicationRunner.java | 10 +++++++ .../kafka/KafkaServicesConfiguration.java | 6 ++++ ...vicesThreadPoolTaskExecutorCustomizer.java | 28 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesThreadPoolTaskExecutorCustomizer.java diff --git a/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesApplicationRunner.java b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesApplicationRunner.java index badaeedc..9d8539f3 100644 --- a/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesApplicationRunner.java +++ b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesApplicationRunner.java @@ -2,6 +2,8 @@ package de.juplo.kafka.chat.backend.implementation.kafka; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.Consumer; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -14,20 +16,28 @@ import org.springframework.stereotype.Component; havingValue = "kafka") @Component @RequiredArgsConstructor +@Slf4j public class KafkaServicesApplicationRunner implements ApplicationRunner { private final ChannelTaskRunner channelTaskRunner; + private final Consumer dataChannelConsumer; + private final Consumer infoChannelConsumer; @Override public void run(ApplicationArguments args) { + log.info("Executing channel-tasks"); channelTaskRunner.executeChannels(); } @PreDestroy public void joinChannels() throws InterruptedException { + log.info("Closing consumers"); + dataChannelConsumer.close(); + infoChannelConsumer.close(); + log.info("Joining channel-tasks"); channelTaskRunner.joinChannels(); } } diff --git a/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesConfiguration.java b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesConfiguration.java index 525c427a..54aa41f1 100644 --- a/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesConfiguration.java +++ b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesConfiguration.java @@ -38,6 +38,12 @@ import java.util.Properties; @Configuration public class KafkaServicesConfiguration { + @Bean + KafkaServicesThreadPoolTaskExecutorCustomizer kafkaServicesThreadPoolTaskExecutorCustomizer() + { + return new KafkaServicesThreadPoolTaskExecutorCustomizer(); + } + @Bean ChannelTaskRunner channelTaskRunner( ChannelTaskExecutor infoChannelTaskExecutor, diff --git a/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesThreadPoolTaskExecutorCustomizer.java b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesThreadPoolTaskExecutorCustomizer.java new file mode 100644 index 00000000..97ea54b9 --- /dev/null +++ b/src/main/java/de/juplo/kafka/chat/backend/implementation/kafka/KafkaServicesThreadPoolTaskExecutorCustomizer.java @@ -0,0 +1,28 @@ +package de.juplo.kafka.chat.backend.implementation.kafka; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + + +/** + * Customizer for the auto-configured Bean {@code applicationTaskExecutor}. + * + * This customization is necessary, because otherwise, the bean is part + * of the lowest phase of the {@link org.springframework.context.SmartLifecycle} + * and, hence, destroyed first during shutdown. + * + * Without this customization, the shutdown of the thread-pool, that is triggered + * this way does not succeed, blocking the furthershutdown for 30 seconds. + */ +@Slf4j +public class KafkaServicesThreadPoolTaskExecutorCustomizer implements ThreadPoolTaskExecutorCustomizer +{ + @Override + public void customize(ThreadPoolTaskExecutor taskExecutor) + { + log.info("Customizing the auto-configured ThreadPoolTaskExecutor"); + taskExecutor.setWaitForTasksToCompleteOnShutdown(true); + taskExecutor.setAwaitTerminationSeconds(10); + } +} -- 2.20.1