diff --git a/src/main/java/vc/Application.java b/src/main/java/vc/Application.java index 0a179ea..24413aa 100644 --- a/src/main/java/vc/Application.java +++ b/src/main/java/vc/Application.java @@ -27,6 +27,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestTemplate; +import javax.annotation.PreDestroy; import java.time.Duration; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -38,6 +39,8 @@ public class Application { @Value("${BOT_TOKEN}") String token; private static final Logger LOGGER = getLogger("Application"); + private GatewayDiscordClient gatewayDiscordClient; + private ScheduledExecutorService scheduledExecutorService; public static void main(String[] args) { new SpringApplicationBuilder(Application.class) @@ -54,12 +57,13 @@ public ClientHttpRequestFactory clientHttpRequestFactory() { @Bean public GatewayDiscordClient gatewayDiscordClient() { - return DiscordClientBuilder.create(token).build() + this.gatewayDiscordClient = DiscordClientBuilder.create(token).build() .gateway() .setEnabledIntents(IntentSet.of(Intent.GUILDS)) .setInitialPresence(ignore -> ClientPresence.of(Status.ONLINE, ClientActivity.custom("/commands"))) .login() .block(); + return this.gatewayDiscordClient; } @Bean @@ -91,12 +95,28 @@ public ObjectMapper objectMapper() { mapper.registerModule(new JsonNullableModule()); return mapper; } + @Bean public ScheduledExecutorService scheduledExecutorService() { - return Executors.newScheduledThreadPool(4, new ThreadFactoryBuilder() + this.scheduledExecutorService = Executors.newScheduledThreadPool(4, new ThreadFactoryBuilder() .setDaemon(true) .setNameFormat("scheduled-%d") .setUncaughtExceptionHandler((t, e) -> LOGGER.error("Uncaught exception in scheduled thread: {}", t.getName(), e)) .build()); + return this.scheduledExecutorService; + } + + @PreDestroy + public void onDestroy() { + try { + if (this.gatewayDiscordClient != null) { + this.gatewayDiscordClient.logout().block(); + } + if (this.scheduledExecutorService != null) { + this.scheduledExecutorService.shutdownNow(); + } + } catch (Exception e) { + LOGGER.error("Error during shutdown", e); + } } } diff --git a/src/main/java/vc/config/GuildConfigDatabase.java b/src/main/java/vc/config/GuildConfigDatabase.java index 2e713b8..35c13cf 100644 --- a/src/main/java/vc/config/GuildConfigDatabase.java +++ b/src/main/java/vc/config/GuildConfigDatabase.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -20,7 +21,7 @@ import java.util.Optional; @Component -public class GuildConfigDatabase { +public class GuildConfigDatabase implements DisposableBean { private static final Logger LOGGER = LoggerFactory.getLogger(GuildConfigDatabase.class); // backups older than this date will be deleted private static final Duration ROLLING_BACKUP_DURATION = Duration.ofDays(7); @@ -48,10 +49,10 @@ public GuildConfigDatabase( LOGGER.error("Error initializing guild config database connection", e); throw new RuntimeException(e); } - Runtime.getRuntime().addShutdownHook(new Thread(this::close)); } - private void close() { + @Override + public void destroy() { try { connection.close(); } catch (final Exception e) { diff --git a/src/main/java/vc/config/RemoteDatabaseBackup.java b/src/main/java/vc/config/RemoteDatabaseBackup.java index 84cb27a..55d80e5 100644 --- a/src/main/java/vc/config/RemoteDatabaseBackup.java +++ b/src/main/java/vc/config/RemoteDatabaseBackup.java @@ -4,6 +4,7 @@ import io.minio.messages.Item; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -13,7 +14,7 @@ import java.util.stream.StreamSupport; @Component -public class RemoteDatabaseBackup { +public class RemoteDatabaseBackup implements DisposableBean { private static final Logger LOGGER = LoggerFactory.getLogger("RemoteDatabaseBackup"); private final String bucketName; private final MinioClient minioClient; @@ -105,4 +106,13 @@ public void downloadDatabaseBackup(final String backupPath) { throw new RuntimeException(e); } } + + @Override + public void destroy() { + try { + minioClient.close(); + } catch (final Exception e) { + LOGGER.error("Error closing Minio client", e); + } + } } diff --git a/src/main/java/vc/live/RedisClient.java b/src/main/java/vc/live/RedisClient.java index 5f86d52..8b9ff48 100644 --- a/src/main/java/vc/live/RedisClient.java +++ b/src/main/java/vc/live/RedisClient.java @@ -4,11 +4,15 @@ import org.redisson.api.RBoundedBlockingQueue; import org.redisson.api.RedissonClient; import org.redisson.config.Config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component -public class RedisClient { +public class RedisClient implements DisposableBean { + private static final Logger LOGGER = LoggerFactory.getLogger(RedisClient.class); private RedissonClient redissonClient; public RedisClient(@Value("${REDIS_URL}") final String redisURL, @Value("${REDIS_USERNAME}") final String redisUsername, @Value("${REDIS_PASSWORD}") final String redisPassword) { @@ -31,4 +35,13 @@ public RedissonClient buildRedisClient(final String redisURL, final String redis .setConnectionMinimumIdleSize(1); return Redisson.create(config); } + + @Override + public void destroy() { + try { + redissonClient.shutdown(); + } catch (Exception e) { + LOGGER.error("Failed to shutdown Redisson client", e); + } + } }