Skip to content

Commit 6c65155

Browse files
authored
Merge pull request #506 from danthe1st/graceful-redeploy
graceful shutdown on redeploy
2 parents ec69e68 + 111ac57 commit 6c65155

File tree

4 files changed

+56
-32
lines changed

4 files changed

+56
-32
lines changed

src/main/java/net/discordjug/javabot/SpringConfig.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import java.util.concurrent.Executors;
66
import java.util.concurrent.ScheduledExecutorService;
77

8-
import javax.sql.DataSource;
9-
108
import org.springframework.context.ApplicationContext;
119
import org.springframework.context.annotation.Bean;
1210
import org.springframework.context.annotation.Configuration;
@@ -19,7 +17,6 @@
1917
import net.discordjug.javabot.annotations.PreRegisteredListener;
2018
import net.discordjug.javabot.data.config.BotConfig;
2119
import net.discordjug.javabot.data.config.SystemsConfig;
22-
import net.discordjug.javabot.data.h2db.DbHelper;
2320
import net.discordjug.javabot.tasks.PresenceUpdater;
2421
import net.dv8tion.jda.api.JDA;
2522
import net.dv8tion.jda.api.JDABuilder;
@@ -36,16 +33,8 @@
3633
@RequiredArgsConstructor
3734
public class SpringConfig {
3835
@Bean
39-
PresenceUpdater standardActivityPresenceUpdater() {
40-
return PresenceUpdater.standardActivities();
41-
}
42-
43-
@Bean
44-
DataSource dataSource(BotConfig config) {
45-
if (config.getSystems().getJdaBotToken().isEmpty()) {
46-
throw new RuntimeException("JDA Token not set. Stopping Bot...");
47-
}
48-
return DbHelper.initDataSource(config);
36+
PresenceUpdater standardActivityPresenceUpdater(ScheduledExecutorService threadPool) {
37+
return PresenceUpdater.standardActivities(threadPool);
4938
}
5039

5140
@Bean
@@ -95,6 +84,10 @@ DIH4JDA initializeDIH4JDA(JDA jda) throws DIH4JDAException {
9584

9685
@Bean
9786
BotConfig botConfig() {
98-
return new BotConfig(Path.of("config"));
87+
BotConfig botConfig = new BotConfig(Path.of("config"));
88+
if (botConfig.getSystems().getJdaBotToken().isEmpty()) {
89+
throw new RuntimeException("JDA Token not set. Stopping Bot...");
90+
}
91+
return botConfig;
9992
}
10093
}

src/main/java/net/discordjug/javabot/data/h2db/DbHelper.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import com.zaxxer.hikari.HikariConfig;
44
import com.zaxxer.hikari.HikariDataSource;
5-
6-
import lombok.Getter;
5+
import jakarta.annotation.PreDestroy;
76
import lombok.RequiredArgsConstructor;
87
import lombok.extern.slf4j.Slf4j;
98
import net.discordjug.javabot.data.config.BotConfig;
@@ -12,6 +11,7 @@
1211

1312
import org.h2.tools.Server;
1413
import org.jetbrains.annotations.NotNull;
14+
import org.springframework.context.annotation.Bean;
1515
import org.springframework.stereotype.Service;
1616

1717
import java.io.IOException;
@@ -32,12 +32,12 @@
3232
/**
3333
* Class that provides helper methods for dealing with the database.
3434
*/
35-
@Getter
3635
@Slf4j
3736
@Service
3837
@RequiredArgsConstructor
3938
public class DbHelper {
40-
private final DataSource dataSource;
39+
private Server server;
40+
private HikariDataSource ds;
4141

4242
/**
4343
* Initializes the data source that'll be used throughout the bot to access
@@ -48,12 +48,12 @@ public class DbHelper {
4848
* @throws IllegalStateException If an error occurs and we're unable to
4949
* start the database.
5050
*/
51-
public static @NotNull HikariDataSource initDataSource(@NotNull BotConfig config) {
51+
@Bean
52+
DataSource initDataSource(@NotNull BotConfig config) {
5253
// Determine if we need to initialize the schema, before starting up the server.
5354
boolean shouldInitSchema = shouldInitSchema(config.getSystems().getHikariConfig().getJdbcUrl());
5455

5556
// Now that we have remembered whether we need to initialize the schema, start up the server.
56-
Server server;
5757
try {
5858
System.setProperty("h2.bindAddress", "127.0.0.1");
5959
server = Server.createTcpServer("-tcpPort", "9122", "-ifNotExists").start();
@@ -66,12 +66,8 @@ public class DbHelper {
6666
hikariConfig.setJdbcUrl(hikariConfigSource.getJdbcUrl());
6767
hikariConfig.setMaximumPoolSize(hikariConfigSource.getMaximumPoolSize());
6868
hikariConfig.setLeakDetectionThreshold(hikariConfigSource.getLeakDetectionThreshold());
69-
HikariDataSource ds = new HikariDataSource(hikariConfig);
69+
ds = new HikariDataSource(hikariConfig);
7070
// Add a shutdown hook to close down the datasource and server when the JVM terminates.
71-
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
72-
ds.close();
73-
server.stop();
74-
}));
7571
if (shouldInitSchema) {
7672
try {
7773
initializeSchema(ds);
@@ -124,4 +120,17 @@ public static void initializeSchema(DataSource dataSource) throws IOException, S
124120
log.info("Successfully initialized H2 database.");
125121
}
126122
}
123+
124+
/**
125+
* Closes the database (server) when the application is shut down.
126+
*/
127+
@PreDestroy
128+
public void stop() {
129+
if (ds != null) {
130+
ds.close();
131+
}
132+
if (server != null) {
133+
server.stop();
134+
}
135+
}
127136
}

src/main/java/net/discordjug/javabot/systems/staff_commands/RedeployCommand.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package net.discordjug.javabot.systems.staff_commands;
22

33
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
4+
5+
import java.util.concurrent.ScheduledExecutorService;
6+
import java.util.concurrent.TimeUnit;
7+
48
import lombok.extern.slf4j.Slf4j;
59
import net.discordjug.javabot.data.config.BotConfig;
610
import net.discordjug.javabot.data.h2db.message_cache.MessageCache;
@@ -12,6 +16,8 @@
1216
import net.dv8tion.jda.api.interactions.commands.build.Commands;
1317

1418
import org.jetbrains.annotations.NotNull;
19+
import org.springframework.boot.SpringApplication;
20+
import org.springframework.context.ConfigurableApplicationContext;
1521

1622
/**
1723
* <h3>This class represents the /redeploy command.</h3>
@@ -26,15 +32,21 @@
2632
public class RedeployCommand extends SlashCommand {
2733
private final MessageCache messageCache;
2834
private final BotConfig botConfig;
35+
private final ScheduledExecutorService asyncPool;
36+
private final ConfigurableApplicationContext applicationContext;
2937

3038
/**
3139
* The constructor of this class, which sets the corresponding {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData}.
3240
* @param messageCache A service managing recent messages
3341
* @param botConfig The main configuration of the bot
42+
* @param asyncPool The thread pool used for asynchronous operations
43+
* @param applicationContext The spring application context
3444
*/
35-
public RedeployCommand(MessageCache messageCache, BotConfig botConfig) {
45+
public RedeployCommand(MessageCache messageCache, BotConfig botConfig, ScheduledExecutorService asyncPool, ConfigurableApplicationContext applicationContext) {
3646
this.messageCache = messageCache;
3747
this.botConfig=botConfig;
48+
this.asyncPool = asyncPool;
49+
this.applicationContext = applicationContext;
3850
setCommandData(Commands.slash("redeploy", "(ADMIN-ONLY) Makes the bot redeploy.")
3951
.setDefaultPermissions(DefaultMemberPermissions.DISABLED)
4052
.setGuildOnly(true)
@@ -51,6 +63,14 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
5163
log.warn("Redeploying... Requested by: " + UserUtils.getUserTag(event.getUser()));
5264
event.reply("**Redeploying...** This may take some time.").queue();
5365
messageCache.synchronize();
54-
System.exit(0);
66+
asyncPool.shutdownNow();
67+
try {
68+
asyncPool.awaitTermination(3, TimeUnit.SECONDS);
69+
event.getJDA().shutdown();
70+
event.getJDA().awaitShutdown(3, TimeUnit.SECONDS);
71+
} catch (InterruptedException e) {
72+
Thread.currentThread().interrupt();
73+
}
74+
SpringApplication.exit(applicationContext, () -> 0);
5575
}
5676
}

src/main/java/net/discordjug/javabot/tasks/PresenceUpdater.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import java.util.List;
1414
import java.util.concurrent.CopyOnWriteArrayList;
15-
import java.util.concurrent.Executors;
1615
import java.util.concurrent.ScheduledExecutorService;
1716
import java.util.concurrent.TimeUnit;
1817
import java.util.function.Function;
@@ -32,7 +31,7 @@ public class PresenceUpdater extends ListenerAdapter {
3231
* The executor that is responsible for the scheduled updates of the bot's
3332
* presence data.
3433
*/
35-
private final ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
34+
private final ScheduledExecutorService threadPool;
3635

3736
/**
3837
* A list of functions that take a reference to the bot's JDA client, and
@@ -64,24 +63,27 @@ public class PresenceUpdater extends ListenerAdapter {
6463
* @param activities The list of activity-producing functions.
6564
* @param delay The amount of time the updater should wait before updating the activity.
6665
* @param delayUnit The unit of time that {@link PresenceUpdater#delay} is counted in.
66+
* @param threadPool The thread pool to use for updating presences
6767
*/
68-
public PresenceUpdater(List<Function<JDA, Activity>> activities, long delay, TimeUnit delayUnit) {
68+
public PresenceUpdater(List<Function<JDA, Activity>> activities, long delay, TimeUnit delayUnit, ScheduledExecutorService threadPool) {
6969
this.activities = new CopyOnWriteArrayList<>(activities);
7070
this.delay = delay;
7171
this.delayUnit = delayUnit;
72+
this.threadPool = threadPool;
7273
}
7374

7475
/**
7576
* A list of standard Activities.
7677
*
78+
* @param threadPool The thread pool to use for updating activities
7779
* @return A pre-built implementation of the {@link PresenceUpdater} that
7880
* has all the necessary properties defined to reasonable defaults.
7981
*/
80-
public static PresenceUpdater standardActivities() {
82+
public static PresenceUpdater standardActivities(ScheduledExecutorService threadPool) {
8183
return new PresenceUpdater(List.of(
8284
jda -> Activity.watching(String.format("%s members", jda.getGuilds().stream().mapToLong(Guild::getMemberCount).sum())),
8385
jda -> Activity.customStatus("Use /report, 'Report User' or 'Report Message' to report disruptive behaviour!")
84-
), 35, TimeUnit.SECONDS);
86+
), 35, TimeUnit.SECONDS, threadPool);
8587
}
8688

8789
/**

0 commit comments

Comments
 (0)