RED-9496 - Implement graceful shutdown #572
@ -0,0 +1,57 @@
|
||||
package com.iqser.red.service.persistence.management.v1.processor.lifecycle;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LifecycleAspect {
|
||||
|
||||
private final LifecycleManager lifecycleManager;
|
||||
|
||||
private final LifecycleProperties lifecycleProperties;
|
||||
|
||||
|
||||
@Around("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener) || "
|
||||
+ "@annotation(org.springframework.web.bind.annotation.GetMapping) || "
|
||||
+ "@annotation(org.springframework.web.bind.annotation.PostMapping) || "
|
||||
+ "@annotation(org.springframework.web.bind.annotation.PutMapping) || "
|
||||
+ "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || "
|
||||
+ "@annotation(org.springframework.web.bind.annotation.RequestMapping))")
|
||||
public Object checkLifecycle(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
|
||||
String targetClassName = joinPoint.getTarget().getClass().getPackageName();
|
||||
if (!targetClassName.startsWith(lifecycleProperties.getBasePackage())) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
synchronized (lifecycleManager) {
|
||||
if (!lifecycleManager.isRunning()) {
|
||||
log.info("Application is shutting down, rejecting new messages.");
|
||||
throw new AmqpRejectAndDontRequeueException("Application is shutting down, rejecting new messages.");
|
||||
}
|
||||
lifecycleManager.incrementAndGet();
|
||||
}
|
||||
|
||||
try {
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
int remainingTasks = lifecycleManager.decrementAndGet();
|
||||
synchronized (lifecycleManager) {
|
||||
if (remainingTasks == 0 && !lifecycleManager.isRunning()) {
|
||||
lifecycleManager.countDown();
|
||||
log.info("All tasks are done, ready for shutdown.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
package com.iqser.red.service.persistence.management.v1.processor.lifecycle;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LifecycleManager implements SmartLifecycle, ApplicationListener<ContextClosedEvent> {
|
||||
|
||||
private volatile boolean running; // by default initialized as false
|
||||
private volatile boolean shutdownInitiated; // by default initialized as false
|
||||
private final Object shutdownMonitor = new Object();
|
||||
private final AtomicInteger activeTasks = new AtomicInteger(0);
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
|
||||
public void countDown() {
|
||||
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
|
||||
public int incrementAndGet() {
|
||||
|
||||
return activeTasks.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
public int decrementAndGet() {
|
||||
|
||||
return activeTasks.decrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
synchronized (shutdownMonitor) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
synchronized (shutdownMonitor) {
|
||||
running = false;
|
||||
if (activeTasks.get() == 0) {
|
||||
latch.countDown(); // No active tasks, release the latch immediately
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isAutoStartup() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getPhase() {
|
||||
|
||||
return Integer.MAX_VALUE; // Start this component last and stop it first
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent event) {
|
||||
|
||||
synchronized (shutdownMonitor) {
|
||||
if (shutdownInitiated) {
|
||||
return; // Avoid multiple shutdown initiations
|
||||
}
|
||||
shutdownInitiated = true;
|
||||
}
|
||||
|
||||
stop();
|
||||
log.info("Context is closing, waiting for ongoing tasks to complete.");
|
||||
try {
|
||||
latch.await(); // Wait for the latch to count down to zero
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("Shutdown was interrupted", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.iqser.red.service.persistence.management.v1.processor.lifecycle;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ConfigurationProperties("lifecycle")
|
||||
public class LifecycleProperties {
|
||||
|
||||
private String basePackage;
|
||||
}
|
||||
@ -24,7 +24,6 @@ dependencies {
|
||||
api(project(":persistence-service-internal-api-impl-v1"))
|
||||
api(project(":persistence-service-shared-mongo-v1"))
|
||||
api("com.iqser.red.commons:storage-commons:2.49.0")
|
||||
api("com.knecon.fforesight:lifecycle-commons:0.6.0")
|
||||
api("junit:junit:4.13.2")
|
||||
api("org.apache.logging.log4j:log4j-slf4j-impl:2.20.0")
|
||||
api("net.logstash.logback:logstash-logback-encoder:7.4")
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
package com.iqser.red.service.peristence.v1.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.tomcat.websocket.server.WsSci;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@ -21,8 +18,6 @@ import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfigurati
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@ -46,13 +41,13 @@ import com.iqser.red.service.dictionarymerge.commons.DictionaryMergeService;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.PersistenceServiceProcessorConfiguration;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.cache.PersistenceServiceExternalApiCacheConfiguration;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.lifecycle.LifecycleProperties;
|
||||
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
|
||||
import com.iqser.red.service.persistence.v1.internal.api.PersistenceServiceInternalApiConfiguration;
|
||||
import com.iqser.red.storage.commons.StorageAutoConfiguration;
|
||||
import com.knecon.fforesight.databasetenantcommons.DatabaseTenantCommonsAutoConfiguration;
|
||||
import com.knecon.fforesight.jobscommons.JobsAutoConfiguration;
|
||||
import com.knecon.fforesight.keycloakcommons.DefaultKeyCloakCommonsAutoConfiguration;
|
||||
import com.knecon.fforesight.lifecyclecommons.LifecycleAutoconfiguration;
|
||||
import com.knecon.fforesight.mongo.database.commons.MongoDatabaseCommonsAutoConfiguration;
|
||||
import com.knecon.fforesight.swaggercommons.SpringDocAutoConfiguration;
|
||||
import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
|
||||
@ -72,9 +67,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@EnableRetry
|
||||
@EnableScheduling
|
||||
@EnableCaching
|
||||
@EnableConfigurationProperties({FileManagementServiceSettings.class})
|
||||
@EnableConfigurationProperties({FileManagementServiceSettings.class, LifecycleProperties.class})
|
||||
@EnableMongoRepositories(basePackages = "com.iqser.red.service.persistence")
|
||||
@ImportAutoConfiguration({StorageAutoConfiguration.class, JobsAutoConfiguration.class, DatabaseTenantCommonsAutoConfiguration.class, MultiTenancyAutoConfiguration.class, SpringDocAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, MongoDatabaseCommonsAutoConfiguration.class, LifecycleAutoconfiguration.class})
|
||||
@ImportAutoConfiguration({StorageAutoConfiguration.class, JobsAutoConfiguration.class, DatabaseTenantCommonsAutoConfiguration.class, MultiTenancyAutoConfiguration.class, SpringDocAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, MongoDatabaseCommonsAutoConfiguration.class})
|
||||
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, CassandraAutoConfiguration.class, DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
|
||||
@Import({PersistenceServiceExternalApiConfigurationV2.class, PersistenceServiceExternalApiConfiguration.class, PersistenceServiceInternalApiConfiguration.class, PersistenceServiceExternalApiCacheConfiguration.class, MultiTenancyWebConfiguration.class, PersistenceServiceProcessorConfiguration.class, MessagingConfiguration.class, MultiTenancyMessagingConfiguration.class})
|
||||
@EnableAspectJAutoProxy
|
||||
|
||||
@ -22,7 +22,7 @@ server:
|
||||
port: 8080
|
||||
|
||||
lifecycle:
|
||||
base-package: com.iqser.red.service.peristence
|
||||
base-package: com.iqser.red.service.persistence
|
||||
|
||||
spring:
|
||||
main:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user