diff --git a/src/main/java/com/eternalcode/discordapp/DiscordApp.java b/src/main/java/com/eternalcode/discordapp/DiscordApp.java index 09751a83..40cf4356 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; @@ -63,11 +64,12 @@ 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; 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)); @@ -90,7 +92,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 = @@ -169,7 +171,6 @@ public static void main(String... args) throws InterruptedException { .awaitReady(); observerRegistry.observe(ExperienceChangeEvent.class, new LevelController(levelConfig, levelService, jda)); - GuildStatisticsService guildStatisticsService = new GuildStatisticsService(config, jda); Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { @@ -177,49 +178,24 @@ public static void main(String... args) throws InterruptedException { LOGGER.error("Uncaught exception", throwable); }); - 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() { 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(); - } + databaseManager.close(); + } + catch (Exception exception) { + throw new RuntimeException(exception); + } - LOGGER.info("Executor service shut down successfully."); + try { + 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/database/DatabaseManager.java b/src/main/java/com/eternalcode/discordapp/database/DatabaseManager.java index b704dfe4..5ff2bf92 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 -> { diff --git a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java index e335b1a3..f5494bcc 100644 --- a/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java +++ b/src/main/java/com/eternalcode/discordapp/guildstats/GuildStatisticsTask.java @@ -1,8 +1,6 @@ package com.eternalcode.discordapp.guildstats; -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 082a7d51..0203ca20 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 00000000..95c86204 --- /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 00000000..e6f297ac --- /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(); + } + } +}