From bc7cac92660deaf914e34659f64d76eb7b105b8a Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 19 Aug 2024 00:09:21 +0200 Subject: [PATCH 1/5] Add DatabaseManager closure on shutdown --- .../java/com/eternalcode/discordapp/DiscordApp.java | 10 +++++++++- .../discordapp/database/DatabaseManager.java | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/eternalcode/discordapp/DiscordApp.java b/src/main/java/com/eternalcode/discordapp/DiscordApp.java index b6a295a..a1c1883 100644 --- a/src/main/java/com/eternalcode/discordapp/DiscordApp.java +++ b/src/main/java/com/eternalcode/discordapp/DiscordApp.java @@ -68,6 +68,7 @@ public class DiscordApp { private static ExperienceService experienceService; private static LevelService levelService; private static GitHubReviewService gitHubReviewService; + private static DatabaseManager databaseManager; public static void main(String... args) throws InterruptedException { Runtime.getRuntime().addShutdownHook(new Thread(DiscordApp::shutdown)); @@ -90,7 +91,7 @@ public static void main(String... args) throws InterruptedException { } try { - DatabaseManager databaseManager = new DatabaseManager(databaseConfig, new File("database")); + databaseManager = new DatabaseManager(databaseConfig, new File("database")); databaseManager.connect(); UserRepositoryImpl.create(databaseManager); GitHubReviewMentionRepository gitHubReviewMentionRepository = @@ -200,6 +201,13 @@ public static void main(String... args) throws InterruptedException { } private static void shutdown() { + try { + databaseManager.close(); + } + catch (Exception exception) { + throw new RuntimeException(exception); + } + try { LOGGER.info("Shutting down executor service..."); EXECUTOR_SERVICE.shutdown(); diff --git a/src/main/java/com/eternalcode/discordapp/database/DatabaseManager.java b/src/main/java/com/eternalcode/discordapp/database/DatabaseManager.java index b704dfe..5ff2bf9 100644 --- a/src/main/java/com/eternalcode/discordapp/database/DatabaseManager.java +++ b/src/main/java/com/eternalcode/discordapp/database/DatabaseManager.java @@ -58,6 +58,17 @@ public ConnectionSource getConnectionSource() { return this.connectionSource; } + public void close() throws Exception { + try { + this.connectionSource.close(); + this.hikariDataSource.close(); + } + catch (SQLException sqlException) { + Sentry.captureException(sqlException); + throw new DataAccessException("Failed to close connection", sqlException); + } + } + @SuppressWarnings("unchecked") public Dao getDao(Class clazz) { Dao dao = this.daoCache.computeIfAbsent(clazz, key -> { From a75f35ba7bb5f5d568a3f7545ebafbb9d28f2776 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 19 Aug 2024 00:25:38 +0200 Subject: [PATCH 2/5] Refactor task scheduling using VirtualThreadScheduler. --- .../eternalcode/discordapp/DiscordApp.java | 47 +++------------- .../guildstats/GuildStatisticsTask.java | 2 +- .../discordapp/review/GitHubReviewTask.java | 4 +- .../discordapp/scheduler/Scheduler.java | 12 ++++ .../scheduler/VirtualThreadSchedulerImpl.java | 56 +++++++++++++++++++ 5 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/eternalcode/discordapp/scheduler/Scheduler.java create mode 100644 src/main/java/com/eternalcode/discordapp/scheduler/VirtualThreadSchedulerImpl.java diff --git a/src/main/java/com/eternalcode/discordapp/DiscordApp.java b/src/main/java/com/eternalcode/discordapp/DiscordApp.java index a1c1883..8091104 100644 --- a/src/main/java/com/eternalcode/discordapp/DiscordApp.java +++ b/src/main/java/com/eternalcode/discordapp/DiscordApp.java @@ -38,6 +38,8 @@ import com.eternalcode.discordapp.review.command.GitHubReviewCommand; import com.eternalcode.discordapp.review.database.GitHubReviewMentionRepository; import com.eternalcode.discordapp.review.database.GitHubReviewMentionRepositoryImpl; +import com.eternalcode.discordapp.scheduler.Scheduler; +import com.eternalcode.discordapp.scheduler.VirtualThreadSchedulerImpl; import com.eternalcode.discordapp.user.UserRepositoryImpl; import com.jagrosh.jdautilities.command.CommandClient; import com.jagrosh.jdautilities.command.CommandClientBuilder; @@ -48,7 +50,6 @@ import java.util.EnumSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.entities.Activity; @@ -69,6 +70,7 @@ public class DiscordApp { private static LevelService levelService; private static GitHubReviewService gitHubReviewService; private static DatabaseManager databaseManager; + private static Scheduler scheduler; public static void main(String... args) throws InterruptedException { Runtime.getRuntime().addShutdownHook(new Thread(DiscordApp::shutdown)); @@ -170,34 +172,11 @@ public static void main(String... args) throws InterruptedException { .awaitReady(); observerRegistry.observe(ExperienceChangeEvent.class, new LevelController(levelConfig, levelService, jda)); - GuildStatisticsService guildStatisticsService = new GuildStatisticsService(config, jda); - EXECUTOR_SERVICE.submit(() -> { - while (true) { - new GuildStatisticsTask(guildStatisticsService).run(); - try { - Thread.sleep(Duration.ofMinutes(5).toMillis()); - } - catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - break; - } - } - }); - - EXECUTOR_SERVICE.submit(() -> { - while (true) { - new GitHubReviewTask(gitHubReviewService, jda).run(); - try { - Thread.sleep(Duration.ofMinutes(5).toMillis()); - } - catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - break; - } - } - }); + scheduler = new VirtualThreadSchedulerImpl(); + scheduler.schedule(new GuildStatisticsTask(guildStatisticsService), Duration.ofMinutes(5)); + scheduler.schedule(new GitHubReviewTask(gitHubReviewService, jda), Duration.ofMinutes(5)); } private static void shutdown() { @@ -209,20 +188,10 @@ private static void shutdown() { } try { - LOGGER.info("Shutting down executor service..."); - EXECUTOR_SERVICE.shutdown(); - - if (!EXECUTOR_SERVICE.awaitTermination(60, TimeUnit.SECONDS)) { - LOGGER.warn("Executor did not terminate in the specified time."); - EXECUTOR_SERVICE.shutdownNow(); - } - - LOGGER.info("Executor service shut down successfully."); + scheduler.shutdown(); } catch (InterruptedException exception) { - LOGGER.error("Shutdown interrupted", exception); - EXECUTOR_SERVICE.shutdownNow(); - Thread.currentThread().interrupt(); + throw new RuntimeException(exception); } } } diff --git a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java index e335b1a..2801c93 100644 --- a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java +++ b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java @@ -2,7 +2,7 @@ import java.util.TimerTask; -public class GuildStatisticsTask extends TimerTask { +public class GuildStatisticsTask implements Runnable { private final GuildStatisticsService guildStatisticsService; diff --git a/src/main/java/com/eternalcode/discordapp/review/GitHubReviewTask.java b/src/main/java/com/eternalcode/discordapp/review/GitHubReviewTask.java index 082a7d5..0203ca2 100644 --- a/src/main/java/com/eternalcode/discordapp/review/GitHubReviewTask.java +++ b/src/main/java/com/eternalcode/discordapp/review/GitHubReviewTask.java @@ -3,9 +3,7 @@ import io.sentry.Sentry; import net.dv8tion.jda.api.JDA; -import java.util.TimerTask; - -public class GitHubReviewTask extends TimerTask { +public class GitHubReviewTask implements Runnable { private final GitHubReviewService gitHubReviewService; private final JDA jda; diff --git a/src/main/java/com/eternalcode/discordapp/scheduler/Scheduler.java b/src/main/java/com/eternalcode/discordapp/scheduler/Scheduler.java new file mode 100644 index 0000000..95c8620 --- /dev/null +++ b/src/main/java/com/eternalcode/discordapp/scheduler/Scheduler.java @@ -0,0 +1,12 @@ +package com.eternalcode.discordapp.scheduler; + +import java.time.Duration; + +public interface Scheduler { + + void schedule(Runnable task, Duration delay); + + void schedule(Runnable task); + + void shutdown() throws InterruptedException; +} diff --git a/src/main/java/com/eternalcode/discordapp/scheduler/VirtualThreadSchedulerImpl.java b/src/main/java/com/eternalcode/discordapp/scheduler/VirtualThreadSchedulerImpl.java new file mode 100644 index 0000000..e6f297a --- /dev/null +++ b/src/main/java/com/eternalcode/discordapp/scheduler/VirtualThreadSchedulerImpl.java @@ -0,0 +1,56 @@ +package com.eternalcode.discordapp.scheduler; + +import java.time.Duration; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class VirtualThreadSchedulerImpl implements Scheduler { + + // works on javca 21+ + private static final ExecutorService EXECUTOR_SERVICE = Executors.newVirtualThreadPerTaskExecutor(); + private static final Logger LOGGER = LoggerFactory.getLogger(VirtualThreadSchedulerImpl.class.getName()); + + @Override + public void schedule(Runnable task, Duration delay) { + EXECUTOR_SERVICE.submit(() -> { + while (!Thread.currentThread().isInterrupted()) { + task.run(); + + try { + Thread.sleep(delay.toMillis()); + } + catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + }); + } + + @Override + public void schedule(Runnable task) { + EXECUTOR_SERVICE.submit(task); + } + + @Override + public void shutdown() { + try { + LOGGER.info("Shutting down executor service..."); + EXECUTOR_SERVICE.shutdown(); + + if (!EXECUTOR_SERVICE.awaitTermination(60, TimeUnit.SECONDS)) { + LOGGER.warn("Executor did not terminate in the specified time."); + EXECUTOR_SERVICE.shutdownNow(); + } + + LOGGER.info("Executor service shut down successfully."); + } + catch (InterruptedException exception) { + LOGGER.error("Shutdown interrupted", exception); + EXECUTOR_SERVICE.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} From fc27fc46fa8c3f54813a5bc0c55445d562eadfd4 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Mon, 19 Aug 2024 00:39:03 +0200 Subject: [PATCH 3/5] Fix inconsistent indentation style --- .../eternalcode/discordapp/DiscordApp.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/eternalcode/discordapp/DiscordApp.java b/src/main/java/com/eternalcode/discordapp/DiscordApp.java index f6662e9..fb9c711 100644 --- a/src/main/java/com/eternalcode/discordapp/DiscordApp.java +++ b/src/main/java/com/eternalcode/discordapp/DiscordApp.java @@ -113,12 +113,12 @@ public static void main(String... args) throws InterruptedException { OkHttpClient httpClient = new OkHttpClient(); FilterService filterService = new FilterService() - .registerFilter(new RenovateForcedPushFilter()); + .registerFilter(new RenovateForcedPushFilter()); CommandClient commandClient = new CommandClientBuilder() - .setOwnerId(config.topOwnerId) - .setActivity(Activity.playing("IntelliJ IDEA")) - .useHelpBuilder(false) + .setOwnerId(config.topOwnerId) + .setActivity(Activity.playing("IntelliJ IDEA")) + .useHelpBuilder(false) // slash commands registry .addSlashCommands( @@ -149,24 +149,24 @@ public static void main(String... args) throws InterruptedException { // Slash commands commandClient, - // Experience system - new ExperienceMessageListener(experienceConfig, experienceService), - new ExperienceReactionListener(experienceConfig, experienceService), + // Experience system + new ExperienceMessageListener(experienceConfig, experienceService), + new ExperienceReactionListener(experienceConfig, experienceService), - // Message filter - new FilterMessageEmbedController(filterService), + // Message filter + new FilterMessageEmbedController(filterService), - // leaderboard - new LeaderboardButtonController(leaderboardService) - ) + // leaderboard + new LeaderboardButtonController(leaderboardService) + ) - .setAutoReconnect(true) - .setHttpClient(httpClient) + .setAutoReconnect(true) + .setHttpClient(httpClient) - .enableIntents(EnumSet.allOf(GatewayIntent.class)) - .setMemberCachePolicy(MemberCachePolicy.ALL) - .enableCache(CacheFlag.ONLINE_STATUS) - .setChunkingFilter(ChunkingFilter.ALL) + .enableIntents(EnumSet.allOf(GatewayIntent.class)) + .setMemberCachePolicy(MemberCachePolicy.ALL) + .enableCache(CacheFlag.ONLINE_STATUS) + .setChunkingFilter(ChunkingFilter.ALL) .build() .awaitReady(); From bb86347adc8d30f2f41829ea8e7d70a011e959c3 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Mon, 19 Aug 2024 00:40:58 +0200 Subject: [PATCH 4/5] Remove useless import. --- .../eternalcode/discordapp/guildstats/GuildStatisticsTask.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java index 2801c93..f5494bc 100644 --- a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java +++ b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java @@ -1,7 +1,5 @@ package com.eternalcode.discordapp.guildstats; -import java.util.TimerTask; - public class GuildStatisticsTask implements Runnable { private final GuildStatisticsService guildStatisticsService; From 651f60755d23d2d5f579df9289b9203e2c3a4d81 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Mon, 19 Aug 2024 01:35:32 +0200 Subject: [PATCH 5/5] Remove unused EXECUTOR_SERVICE constant. --- src/main/java/com/eternalcode/discordapp/DiscordApp.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/eternalcode/discordapp/DiscordApp.java b/src/main/java/com/eternalcode/discordapp/DiscordApp.java index fb9c711..40cf435 100644 --- a/src/main/java/com/eternalcode/discordapp/DiscordApp.java +++ b/src/main/java/com/eternalcode/discordapp/DiscordApp.java @@ -64,7 +64,6 @@ public class DiscordApp { private static final Logger LOGGER = LoggerFactory.getLogger(DiscordApp.class); - private static final ExecutorService EXECUTOR_SERVICE = Executors.newVirtualThreadPerTaskExecutor(); private static ExperienceService experienceService; private static LevelService levelService;