From 56cb74815ce1076522381a973a60058409e04dcd Mon Sep 17 00:00:00 2001 From: Vankka Date: Wed, 4 Dec 2024 23:15:16 +0200 Subject: [PATCH] Improve lifecycle --- .../com/discordsrv/api/DiscordSRVApi.java | 48 +--- .../lifecycle/DiscordSRVReloadedEvent.java | 10 +- .../com/discordsrv/api/module/Module.java | 3 +- .../com/discordsrv/api/reload/ReloadFlag.java | 62 +++++ .../discordsrv/api/reload/ReloadResult.java | 44 ++++ .../discordsrv/bukkit/BukkitDiscordSRV.java | 4 +- .../bukkit/DiscordSRVBukkitBootstrap.java | 2 +- .../discordsrv/common/AbstractDiscordSRV.java | 242 +++++++++--------- .../com/discordsrv/common/DiscordSRV.java | 6 +- .../bootstrap/LifecycleManager.java | 29 ++- .../abstraction/sync/AbstractSyncModule.java | 4 +- .../command/game/GameCommandModule.java | 5 +- .../subcommand/reload/ReloadCommand.java | 105 ++++---- .../subcommand/reload/ReloadResults.java | 31 --- .../common/core/module/ModuleManager.java | 29 +-- .../core/module/type/ModuleDelegate.java | 4 +- .../connection/DiscordConnectionManager.java | 28 +- .../connection/jda/JDAConnectionManager.java | 56 ++-- .../common/feature/DiscordInviteModule.java | 4 +- .../common/feature/PresenceUpdaterModule.java | 4 +- .../feature/channel/TimedUpdaterModule.java | 4 +- .../common/feature/console/ConsoleModule.java | 4 +- .../customcommands/CustomCommandModule.java | 4 +- .../requirelinking/RequiredLinkingModule.java | 4 +- 24 files changed, 377 insertions(+), 359 deletions(-) create mode 100644 api/src/main/java/com/discordsrv/api/reload/ReloadFlag.java create mode 100644 api/src/main/java/com/discordsrv/api/reload/ReloadResult.java delete mode 100644 common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadResults.java diff --git a/api/src/main/java/com/discordsrv/api/DiscordSRVApi.java b/api/src/main/java/com/discordsrv/api/DiscordSRVApi.java index 743db03e..3a2febd7 100644 --- a/api/src/main/java/com/discordsrv/api/DiscordSRVApi.java +++ b/api/src/main/java/com/discordsrv/api/DiscordSRVApi.java @@ -38,8 +38,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.Predicate; +import java.util.Optional; /** * The DiscordSRV API. @@ -156,7 +155,6 @@ public interface DiscordSRVApi { *

* JDA is an external library and comes with its own versioning and deprecation policies, using DiscordSRV's own APIs where possible is recommended. * DiscordSRV will upgrade JDA as needed, including breaking changes and major version upgrades. - * * JDA's deprecation policy * * @see #discordAPI() discordAPI() for the first party api @@ -271,50 +269,6 @@ public interface DiscordSRVApi { } - enum ReloadFlag { - CONFIG(false), - LINKED_ACCOUNT_PROVIDER(false), - STORAGE(true), - DISCORD_CONNECTION(DiscordSRVApi::isReady), - MODULES(false), - DISCORD_COMMANDS(false), - - // Bukkit only - TRANSLATIONS(false) - - ; - - public static final Set ALL = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(values()))); - public static final Set DEFAULT_FLAGS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(CONFIG, MODULES))); - - private final Predicate requiresConfirm; - - ReloadFlag(boolean requiresConfirm) { - this(__ -> requiresConfirm); - } - - ReloadFlag(Predicate requiresConfirm) { - this.requiresConfirm = requiresConfirm; - } - - public boolean requiresConfirm(DiscordSRVApi discordSRV) { - return requiresConfirm.test(discordSRV); - } - } - - interface ReloadResult { - - ReloadResult RESTART_REQUIRED = DefaultConstants.RESTART_REQUIRED; - - String name(); - - enum DefaultConstants implements ReloadResult { - - RESTART_REQUIRED - - } - } - @ApiStatus.Internal @SuppressWarnings("unused") // API, Reflection final class InstanceHolder { diff --git a/api/src/main/java/com/discordsrv/api/events/lifecycle/DiscordSRVReloadedEvent.java b/api/src/main/java/com/discordsrv/api/events/lifecycle/DiscordSRVReloadedEvent.java index 681b9fe0..6770272f 100644 --- a/api/src/main/java/com/discordsrv/api/events/lifecycle/DiscordSRVReloadedEvent.java +++ b/api/src/main/java/com/discordsrv/api/events/lifecycle/DiscordSRVReloadedEvent.java @@ -23,8 +23,8 @@ package com.discordsrv.api.events.lifecycle; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.events.Event; +import com.discordsrv.api.reload.ReloadFlag; import java.util.Set; @@ -33,17 +33,17 @@ import java.util.Set; */ public class DiscordSRVReloadedEvent implements Event { - private final Set flags; + private final Set flags; - public DiscordSRVReloadedEvent(Set flags) { + public DiscordSRVReloadedEvent(Set flags) { this.flags = flags; } /** * Set of DiscordSRV systems that were reloaded. - * @return an unmodifiable set of {@link com.discordsrv.api.DiscordSRVApi.ReloadFlag}s + * @return an unmodifiable set of {@link ReloadFlag}s */ - public Set flags() { + public Set flags() { return flags; } } diff --git a/api/src/main/java/com/discordsrv/api/module/Module.java b/api/src/main/java/com/discordsrv/api/module/Module.java index 433d3add..2a299471 100644 --- a/api/src/main/java/com/discordsrv/api/module/Module.java +++ b/api/src/main/java/com/discordsrv/api/module/Module.java @@ -30,6 +30,7 @@ import com.discordsrv.api.discord.connection.details.DiscordMemberCachePolicy; import com.discordsrv.api.eventbus.EventListener; import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.events.discord.message.AbstractDiscordMessageEvent; +import com.discordsrv.api.reload.ReloadResult; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.guild.member.GenericGuildMemberEvent; import net.dv8tion.jda.api.requests.GatewayIntent; @@ -161,5 +162,5 @@ public interface Module { * Called by DiscordSRV to reload this module. This is called when the module is enabled as well. * @param resultConsumer a consumer to supply results to, if any apply */ - default void reload(Consumer resultConsumer) {} + default void reload(Consumer resultConsumer) {} } diff --git a/api/src/main/java/com/discordsrv/api/reload/ReloadFlag.java b/api/src/main/java/com/discordsrv/api/reload/ReloadFlag.java new file mode 100644 index 00000000..e4cb9cc1 --- /dev/null +++ b/api/src/main/java/com/discordsrv/api/reload/ReloadFlag.java @@ -0,0 +1,62 @@ +/* + * This file is part of the DiscordSRV API, licensed under the MIT License + * Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.discordsrv.api.reload; + +import com.discordsrv.api.DiscordSRVApi; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Predicate; + +public enum ReloadFlag { + CONFIG(false), + LINKED_ACCOUNT_PROVIDER(false), + STORAGE(true), + DISCORD_CONNECTION(DiscordSRVApi::isReady), + DISCORD_COMMANDS(false), + + // Bukkit only + TRANSLATIONS(false); + + public static final Set ALL = Collections.unmodifiableSet( + new LinkedHashSet<>(Arrays.asList(values()))); + public static final Set DEFAULT_FLAGS = Collections.unmodifiableSet( + new LinkedHashSet<>(Collections.singletonList(CONFIG))); + + private final Predicate requiresConfirm; + + ReloadFlag(boolean requiresConfirm) { + this(__ -> requiresConfirm); + } + + ReloadFlag(Predicate requiresConfirm) { + this.requiresConfirm = requiresConfirm; + } + + public boolean requiresConfirm(DiscordSRVApi discordSRV) { + return requiresConfirm.test(discordSRV); + } +} diff --git a/api/src/main/java/com/discordsrv/api/reload/ReloadResult.java b/api/src/main/java/com/discordsrv/api/reload/ReloadResult.java new file mode 100644 index 00000000..1bd5d4ae --- /dev/null +++ b/api/src/main/java/com/discordsrv/api/reload/ReloadResult.java @@ -0,0 +1,44 @@ +/* + * This file is part of the DiscordSRV API, licensed under the MIT License + * Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.discordsrv.api.reload; + +import org.jetbrains.annotations.ApiStatus; + +public enum ReloadResult { + + RESTART_REQUIRED, + + @ApiStatus.Internal + ERROR, + @ApiStatus.Internal + DEFAULT_BOT_TOKEN, + @ApiStatus.Internal + DISCORD_CONNECTION_RELOAD_REQUIRED, + @ApiStatus.Internal + SECURITY_FAILED, + @ApiStatus.Internal + STORAGE_CONNECTION_FAILED, + @ApiStatus.Internal + DISCORD_CONNECTION_FAILED, +} diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java index eb345690..7862a44e 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java @@ -19,6 +19,8 @@ package com.discordsrv.bukkit; import com.discordsrv.api.DiscordSRVApi; +import com.discordsrv.api.reload.ReloadFlag; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.bukkit.ban.BukkitBanModule; import com.discordsrv.bukkit.command.game.BukkitGameCommandExecutionHelper; import com.discordsrv.bukkit.command.game.handler.AbstractBukkitCommandHandler; @@ -226,7 +228,7 @@ public class BukkitDiscordSRV extends AbstractDiscordSRV reload(Set flags, boolean initial) throws Throwable { + protected List reload(Set flags, boolean initial) throws Throwable { List results = super.reload(flags, initial); if (flags.contains(ReloadFlag.TRANSLATIONS)) { diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/DiscordSRVBukkitBootstrap.java b/bukkit/src/main/java/com/discordsrv/bukkit/DiscordSRVBukkitBootstrap.java index 7af67817..b4280c59 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/DiscordSRVBukkitBootstrap.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/DiscordSRVBukkitBootstrap.java @@ -71,7 +71,7 @@ public class DiscordSRVBukkitBootstrap extends BukkitBootstrap implements IBoots lifecycleManager.loadAndEnable(() -> this.discordSRV = new BukkitDiscordSRV(this)); if (discordSRV == null) return; - discordSRV.scheduler().runOnMainThreadLaterInTicks(() -> discordSRV.invokeServerStarted(), 1); + discordSRV.scheduler().runOnMainThreadLaterInTicks(() -> discordSRV.runServerStarted(), 1); } @Override diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index 3e2c727b..9d74e067 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -23,10 +23,11 @@ import com.discordsrv.api.events.lifecycle.DiscordSRVReadyEvent; import com.discordsrv.api.events.lifecycle.DiscordSRVReloadedEvent; import com.discordsrv.api.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.module.Module; +import com.discordsrv.api.reload.ReloadFlag; import com.discordsrv.common.abstraction.bootstrap.IBootstrap; import com.discordsrv.common.command.discord.DiscordCommandModule; import com.discordsrv.common.command.game.GameCommandModule; -import com.discordsrv.common.command.game.commands.subcommand.reload.ReloadResults; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.config.configurate.manager.ConnectionConfigManager; import com.discordsrv.common.config.configurate.manager.MainConfigManager; import com.discordsrv.common.config.configurate.manager.MessagesConfigManager; @@ -55,6 +56,7 @@ import com.discordsrv.common.core.storage.StorageType; import com.discordsrv.common.core.storage.impl.MemoryStorage; import com.discordsrv.common.discord.api.DiscordAPIEventModule; import com.discordsrv.common.discord.api.DiscordAPIImpl; +import com.discordsrv.common.discord.connection.DiscordConnectionManager; import com.discordsrv.common.discord.connection.details.DiscordConnectionDetailsImpl; import com.discordsrv.common.discord.connection.jda.JDAConnectionManager; import com.discordsrv.common.exception.StorageException; @@ -90,10 +92,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDAInfo; -import okhttp3.ConnectionPool; -import okhttp3.Dispatcher; -import okhttp3.OkHttpClient; -import okhttp3.Request; +import okhttp3.*; import org.apache.commons.lang3.StringUtils; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -136,6 +135,7 @@ public abstract class AbstractDiscordSRV< private final AtomicReference status = new AtomicReference<>(Status.INITIALIZED); private final AtomicReference beenReady = new AtomicReference<>(false); private boolean serverStarted = false; + private final Object reloadLock = new Object(); // DiscordSRVApi private EventBusImpl eventBus; @@ -543,37 +543,72 @@ public abstract class AbstractDiscordSRV< // Lifecycle + public boolean isServerStarted() { + return serverStarted; + } + public ZonedDateTime getInitializeTime() { return initializeTime; } + /** + * Run blocking when plugin/mod is triggered for enabling. + */ @Override public final void runEnable() { try { this.enable(); } catch (Throwable t) { - logger.error("Failed to enable", t); + logger().error("Failed to enable", t); setStatus(Status.FAILED_TO_START); } } + /** + * Must be manually triggered for {@link DiscordSRV.ServerType#SERVER}, automatically triggered in {@link #enable()} for {@link DiscordSRV.ServerType#PROXY}. + * @return a future running on the {@link #scheduler()} + */ + public final CompletableFuture runServerStarted() { + return scheduler().execute(() -> { + if (status().isShutdown()) { + // Already shutdown/shutting down, don't bother + return; + } + try { + this.serverStarted(); + } catch (Throwable t) { + if (status().isShutdown() && t instanceof NoClassDefFoundError) { + // Already shutdown, ignore errors for classes that already got unloaded + return; + } + logger().error("Failed to start", t); + setStatus(Status.FAILED_TO_START); + + disable(); + } + }); + } + + /** + * Triggers a reload of DiscordSRV. + * @param flags the targets to reload + * @return the results of the reload + */ @Override - public final CompletableFuture invokeDisable() { - return scheduler().execute(this::disable); + public final List runReload(Set flags) { + try { + synchronized (reloadLock) { + return reload(flags, false); + } + } catch (Throwable e) { + logger.error("Failed to reload", e); + return Collections.singletonList(ReloadResult.ERROR); + } } @Override - public final List runReload(Set flags, boolean silent) { - try { - return reload(flags, silent); - } catch (Throwable e) { - if (silent) { - throw new RuntimeException(e); - } else { - logger.error("Failed to reload", e); - } - return Collections.singletonList(ReloadResults.FAILED); - } + public final CompletableFuture runDisable() { + return scheduler().execute(this::disable); } @MustBeInvokedByOverriders @@ -643,41 +678,16 @@ public abstract class AbstractDiscordSRV< } // Initial load - try { - runReload(ReloadFlag.ALL, true); - } catch (RuntimeException e) { - throw e.getCause(); - } + reload(ReloadFlag.ALL, true); if (serverType() == ServerType.PROXY) { - invokeServerStarted().get(); + runServerStarted().get(); } // Register PlayerProvider listeners playerProvider().subscribe(); } - public final CompletableFuture invokeServerStarted() { - return scheduler().supply(() -> { - if (status().isShutdown()) { - // Already shutdown/shutting down, don't bother - return null; - } - try { - this.serverStarted(); - } catch (Throwable t) { - if (status().isShutdown() && t instanceof NoClassDefFoundError) { - // Already shutdown, ignore errors for classes that already got unloaded - return null; - } - setStatus(Status.FAILED_TO_START); - disable(); - logger().error("Failed to start", t); - } - return null; - }); - } - @MustBeInvokedByOverriders protected void serverStarted() { serverStarted = true; @@ -688,50 +698,8 @@ public abstract class AbstractDiscordSRV< Optional.ofNullable(getModule(PresenceUpdaterModule.class)).ifPresent(PresenceUpdaterModule::serverStarted); } - public boolean isServerStarted() { - return serverStarted; - } - - private StorageType getStorageType() { - String backend = connectionConfig().storage.backend; - switch (backend.toLowerCase(Locale.ROOT)) { - case "h2": return StorageType.H2; - case "mysql": return StorageType.MYSQL; - case "mariadb": return StorageType.MARIADB; - } - if (backend.equals(MemoryStorage.IDENTIFIER)) { - return StorageType.MEMORY; - } - throw new StorageException("Unknown storage backend \"" + backend + "\""); - } - @MustBeInvokedByOverriders - protected void disable() { - Status status = this.status.get(); - if (status == Status.INITIALIZED || status.isShutdown()) { - // Hasn't started or already shutting down/shutdown - return; - } - this.status.set(Status.SHUTTING_DOWN); - - // Unregister PlayerProvider listeners - playerProvider().unsubscribe(); - - eventBus().publish(new DiscordSRVShuttingDownEvent()); - eventBus().shutdown(); - try { - if (storage != null) { - storage.close(); - } - } catch (Throwable t) { - logger().error("Failed to close storage connection", t); - } - temporaryLocalData.save(); - this.status.set(Status.SHUTDOWN); - } - - @MustBeInvokedByOverriders - public List reload(Set flags, boolean initial) throws Throwable { + protected List reload(Set flags, boolean initial) throws Throwable { if (!initial) { logger().info("Reloading DiscordSRV..."); } @@ -767,7 +735,7 @@ public abstract class AbstractDiscordSRV< logger().info(""); } discordConnectionManager.invalidToken(true); - results.add(ReloadResults.DEFAULT_BOT_TOKEN); + results.add(ReloadResult.DEFAULT_BOT_TOKEN); return results; } @@ -776,13 +744,13 @@ public abstract class AbstractDiscordSRV< if (updateConfig.security.enabled) { if (updateChecker.isSecurityFailed()) { // Security has already failed - return Collections.singletonList(ReloadResults.SECURITY_FAILED); + return Collections.singletonList(ReloadResult.SECURITY_FAILED); } if (initial && !updateChecker.check(true)) { // Security failed cancel startup & shutdown - invokeDisable(); - return Collections.singletonList(ReloadResults.SECURITY_FAILED); + runDisable(); + return Collections.singletonList(ReloadResult.SECURITY_FAILED); } } else if (initial) { // Not using security, run update check off thread @@ -824,7 +792,7 @@ public abstract class AbstractDiscordSRV< if (initial) { setStatus(Status.FAILED_TO_START); } - return Collections.singletonList(ReloadResults.STORAGE_CONNECTION_FAILED); + return Collections.singletonList(ReloadResult.STORAGE_CONNECTION_FAILED); } } @@ -854,7 +822,7 @@ public abstract class AbstractDiscordSRV< break; default: { linkProvider = null; - logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will not be used"); + logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will be disabled"); break; } } @@ -865,35 +833,20 @@ public abstract class AbstractDiscordSRV< } if (flags.contains(ReloadFlag.DISCORD_CONNECTION)) { - try { - if (discordConnectionManager.instance() != null) { - discordConnectionManager.reconnect().get(); - } else { - discordConnectionManager.connect().get(); + // Shutdown will not fail even if not connected + discordConnectionManager.shutdown(DiscordConnectionManager.DEFAULT_SHUTDOWN_TIMEOUT); + + discordConnectionManager.connect(); + if (!initial) { + waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS); + if (status() != Status.CONNECTED) { + return Collections.singletonList(ReloadResult.DISCORD_CONNECTION_FAILED); } - if (!initial) { - waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS); - if (status() != Status.CONNECTED) { - return Collections.singletonList(ReloadResults.DISCORD_CONNECTION_FAILED); - } - } else { - JDA jda = jda(); - if (jda != null) { - try { - jda.awaitReady(); - } catch (IllegalStateException ignored) { - // JDA shutdown -> don't continue - return Collections.singletonList(ReloadResults.DISCORD_CONNECTION_FAILED); - } - } - } - } catch (ExecutionException e) { - throw e.getCause(); } } // Modules are reloaded upon DiscordSRV being ready, thus not needed at initial - if (!initial && flags.contains(ReloadFlag.MODULES)) { + if (!initial && flags.contains(ReloadFlag.CONFIG)) { results.addAll(moduleManager.reload()); } @@ -907,7 +860,56 @@ public abstract class AbstractDiscordSRV< logger().info("Reload complete."); } - results.add(ReloadResults.SUCCESS); return results; } + + private StorageType getStorageType() { + String backend = connectionConfig().storage.backend; + switch (backend.toLowerCase(Locale.ROOT)) { + case "h2": return StorageType.H2; + case "mysql": return StorageType.MYSQL; + case "mariadb": return StorageType.MARIADB; + } + if (backend.equals(MemoryStorage.IDENTIFIER)) { + return StorageType.MEMORY; + } + throw new StorageException("Unknown storage backend \"" + backend + "\""); + } + + @SuppressWarnings("resource") // + @MustBeInvokedByOverriders + protected void disable() { + Status status = this.status.get(); + if (status == Status.INITIALIZED || status.isShutdown()) { + // Hasn't started or already shutting down/shutdown + return; + } + this.status.set(Status.SHUTTING_DOWN); + + // Unregister PlayerProvider listeners + playerProvider().unsubscribe(); + + eventBus().publish(new DiscordSRVShuttingDownEvent()); + eventBus().shutdown(); + + // Shutdown OkHttp + httpClient.dispatcher().executorService().shutdownNow(); + httpClient.connectionPool().evictAll(); + try { + Cache cache = httpClient.cache(); + if (cache != null) { + cache.close(); + } + } catch (IOException ignored) {} + + try { + if (storage != null) { + storage.close(); + } + } catch (Throwable t) { + logger().error("Failed to close storage connection", t); + } + temporaryLocalData.save(); + this.status.set(Status.SHUTDOWN); + } } diff --git a/common/src/main/java/com/discordsrv/common/DiscordSRV.java b/common/src/main/java/com/discordsrv/common/DiscordSRV.java index 085bd947..a3cb87e2 100644 --- a/common/src/main/java/com/discordsrv/common/DiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/DiscordSRV.java @@ -20,6 +20,7 @@ package com.discordsrv.common; import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.module.Module; +import com.discordsrv.api.reload.ReloadFlag; import com.discordsrv.api.placeholder.format.PlainPlaceholderFormat; import com.discordsrv.common.abstraction.bootstrap.IBootstrap; import com.discordsrv.common.abstraction.player.IPlayer; @@ -28,6 +29,7 @@ import com.discordsrv.common.abstraction.plugin.PluginManager; import com.discordsrv.common.command.game.abstraction.GameCommandExecutionHelper; import com.discordsrv.common.command.game.abstraction.handler.ICommandHandler; import com.discordsrv.common.command.game.abstraction.sender.ICommandSender; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.config.configurate.manager.ConnectionConfigManager; import com.discordsrv.common.config.configurate.manager.MainConfigManager; import com.discordsrv.common.config.configurate.manager.MessagesConfigManager; @@ -174,8 +176,8 @@ public interface DiscordSRV extends DiscordSRVApi { // Lifecycle void runEnable(); - List runReload(Set flags, boolean silent); - CompletableFuture invokeDisable(); + List runReload(Set flags); + CompletableFuture runDisable(); boolean isServerStarted(); ZonedDateTime getInitializeTime(); diff --git a/common/src/main/java/com/discordsrv/common/abstraction/bootstrap/LifecycleManager.java b/common/src/main/java/com/discordsrv/common/abstraction/bootstrap/LifecycleManager.java index 5a91990d..c133096c 100644 --- a/common/src/main/java/com/discordsrv/common/abstraction/bootstrap/LifecycleManager.java +++ b/common/src/main/java/com/discordsrv/common/abstraction/bootstrap/LifecycleManager.java @@ -18,7 +18,7 @@ package com.discordsrv.common.abstraction.bootstrap; -import com.discordsrv.api.DiscordSRVApi; +import com.discordsrv.api.reload.ReloadFlag; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.core.dependency.DependencyLoader; import com.discordsrv.common.core.logging.Logger; @@ -44,7 +44,7 @@ public class LifecycleManager { private final Logger logger; private final ExecutorService taskPool; private final DependencyLoader dependencyLoader; - private final CompletableFuture completableFuture; + private final CompletableFuture dependencyLoadFuture; public LifecycleManager( Logger logger, @@ -66,19 +66,26 @@ public class LifecycleManager { classpathAppender, resourcePaths.toArray(new String[0]) ); - this.completableFuture = dependencyLoader.download(); - this.completableFuture.whenComplete((v, t) -> taskPool.shutdownNow()); + this.dependencyLoadFuture = dependencyLoader.download(); + this.dependencyLoadFuture.whenComplete((v, t) -> taskPool.shutdownNow()); } public void loadAndEnable(Supplier discordSRVSupplier) { - if (relocateAndLoad()) { - discordSRVSupplier.get().runEnable(); + if (!relocateAndLoad()) { + return; } + + DiscordSRV discordSRV = discordSRVSupplier.get(); + if (discordSRV == null) { + return; + } + + discordSRV.runEnable(); } private boolean relocateAndLoad() { try { - completableFuture.get(); + dependencyLoadFuture.get(); dependencyLoader.relocateAndLoad(false).get(); return true; } catch (InterruptedException ignored) { @@ -93,12 +100,12 @@ public class LifecycleManager { if (discordSRV == null) { return; } - discordSRV.runReload(DiscordSRVApi.ReloadFlag.DEFAULT_FLAGS, false); + discordSRV.runReload(ReloadFlag.DEFAULT_FLAGS); } public void disable(DiscordSRV discordSRV) { - if (!completableFuture.isDone()) { - completableFuture.cancel(true); + if (!dependencyLoadFuture.isDone()) { + dependencyLoadFuture.cancel(true); return; } @@ -107,7 +114,7 @@ public class LifecycleManager { } try { - discordSRV.invokeDisable().get(/*15, TimeUnit.SECONDS*/); + discordSRV.runDisable().get(/*15, TimeUnit.SECONDS*/); } catch (InterruptedException/* | TimeoutException*/ e) { logger.warning("Timed out/interrupted shutting down DiscordSRV"); } catch (ExecutionException e) { diff --git a/common/src/main/java/com/discordsrv/common/abstraction/sync/AbstractSyncModule.java b/common/src/main/java/com/discordsrv/common/abstraction/sync/AbstractSyncModule.java index 15030f4e..2e1233f7 100644 --- a/common/src/main/java/com/discordsrv/common/abstraction/sync/AbstractSyncModule.java +++ b/common/src/main/java/com/discordsrv/common/abstraction/sync/AbstractSyncModule.java @@ -18,8 +18,8 @@ package com.discordsrv.common.abstraction.sync; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.eventbus.Subscribe; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.abstraction.player.IPlayer; import com.discordsrv.common.abstraction.sync.cause.GenericSyncCauses; @@ -100,7 +100,7 @@ public abstract class AbstractSyncModule< } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { synchronized (syncs) { syncs.values().forEach(future -> { if (future != null) { diff --git a/common/src/main/java/com/discordsrv/common/command/game/GameCommandModule.java b/common/src/main/java/com/discordsrv/common/command/game/GameCommandModule.java index 5aff6090..df669484 100644 --- a/common/src/main/java/com/discordsrv/common/command/game/GameCommandModule.java +++ b/common/src/main/java/com/discordsrv/common/command/game/GameCommandModule.java @@ -19,11 +19,12 @@ package com.discordsrv.common.command.game; import com.discordsrv.api.DiscordSRVApi; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.command.combined.commands.LinkOtherCommand; import com.discordsrv.common.command.game.abstraction.command.GameCommand; -import com.discordsrv.common.command.game.commands.DiscordSRVGameCommand; import com.discordsrv.common.command.game.abstraction.handler.ICommandHandler; +import com.discordsrv.common.command.game.commands.DiscordSRVGameCommand; import com.discordsrv.common.config.main.GameCommandConfig; import com.discordsrv.common.core.module.type.AbstractModule; @@ -46,7 +47,7 @@ public class GameCommandModule extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { GameCommandConfig config = discordSRV.config().gameCommand; if (config == null) { return; diff --git a/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadCommand.java b/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadCommand.java index 7774917f..9e40d0fc 100644 --- a/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadCommand.java +++ b/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadCommand.java @@ -18,7 +18,8 @@ package com.discordsrv.common.command.game.commands.subcommand.reload; -import com.discordsrv.api.DiscordSRVApi; +import com.discordsrv.api.reload.ReloadFlag; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.abstraction.player.IPlayer; import com.discordsrv.common.command.game.abstraction.command.GameCommand; @@ -65,9 +66,9 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester @Override public void execute(ICommandSender sender, GameCommandArguments arguments, String label) { AtomicBoolean dangerousFlags = new AtomicBoolean(false); - Set flags = getFlagsFromArguments(sender, arguments, dangerousFlags); + Set flags = getFlagsFromArguments(sender, arguments, dangerousFlags); if (flags == null) { - flags = DiscordSRV.ReloadFlag.DEFAULT_FLAGS; + flags = ReloadFlag.DEFAULT_FLAGS; } if (dangerousFlags.get()) { @@ -80,50 +81,64 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester return; } - List results = discordSRV.runReload(flags, false); - for (DiscordSRV.ReloadResult result : results) { - String res = result.name(); - if (res.equals(ReloadResults.FAILED.name())) { - sender.sendMessage( - Component.text() - .append(Component.text("Reload failed.", NamedTextColor.DARK_RED, TextDecoration.BOLD)) - .append(Component.text("Please check the server console/log for more details.")) - ); - } else if (res.equals(ReloadResults.SECURITY_FAILED.name())) { - sender.sendMessage(Component.text( - "DiscordSRV is disabled due to a security check failure. " - + "Please check console for more details", NamedTextColor.DARK_RED)); - } else if (res.equals(ReloadResults.SUCCESS.name())) { - sender.sendMessage(Component.text("Reload successful", NamedTextColor.GRAY)); - } else if (res.equals(ReloadResults.RESTART_REQUIRED.name())) { - sender.sendMessage(Component.text("Some changes require a server restart")); - } else if (res.equals(ReloadResults.STORAGE_CONNECTION_FAILED.name())) { - sender.sendMessage(Component.text("Storage connection failed, please check console for details.", NamedTextColor.RED)); - } else if (res.equals(ReloadResults.DISCORD_CONNECTION_FAILED.name())) { - sender.sendMessage(Component.text("Discord connection failed, please check console for details.", NamedTextColor.RED)); - } else if (res.equals(ReloadResults.DISCORD_CONNECTION_RELOAD_REQUIRED.name())) { - String command = "discordsrv reload " + DiscordSRVApi.ReloadFlag.DISCORD_CONNECTION.name().toLowerCase(Locale.ROOT) + " -confirm"; - Component child; - if (sender instanceof IPlayer) { - child = Component.text("[Click to reload Discord connection]", NamedTextColor.DARK_RED) - .clickEvent(ClickEvent.runCommand("/" + command)) - .hoverEvent(HoverEvent.showText(Component.text("/" + command))); - } else { - child = Component.text("Run ", NamedTextColor.DARK_RED) - .append(Component.text(command, NamedTextColor.GRAY)) - .append(Component.text(" to reload the Discord connection")); + List results = discordSRV.runReload(flags); + if (results.isEmpty()) { + sender.sendMessage(Component.text("Reload successful", NamedTextColor.GRAY)); + return; + } + for (ReloadResult result : results) { + switch (result) { + case ERROR: { + sender.sendMessage( + Component.text() + .append(Component.text("Reload failed.", NamedTextColor.DARK_RED, TextDecoration.BOLD)) + .append(Component.text("Please check the server console/log for more details.")) + ); + break; } + case SECURITY_FAILED: { + sender.sendMessage(Component.text( + "DiscordSRV is disabled due to a security check failure. " + + "Please check console for more details", NamedTextColor.DARK_RED)); + break; + } + case RESTART_REQUIRED: { + sender.sendMessage(Component.text("Some changes require a server restart")); + break; + } + case STORAGE_CONNECTION_FAILED: { + sender.sendMessage(Component.text("Storage connection failed, please check console for details.", NamedTextColor.RED)); + break; + } + case DISCORD_CONNECTION_FAILED: { + sender.sendMessage(Component.text("Discord connection failed, please check console for details.", NamedTextColor.RED)); + break; + } + case DISCORD_CONNECTION_RELOAD_REQUIRED: { + String command = "discordsrv reload " + ReloadFlag.DISCORD_CONNECTION.name().toLowerCase(Locale.ROOT) + " -confirm"; + Component child; + if (sender instanceof IPlayer) { + child = Component.text("[Click to reload Discord connection]", NamedTextColor.DARK_RED) + .clickEvent(ClickEvent.runCommand("/" + command)) + .hoverEvent(HoverEvent.showText(Component.text("/" + command))); + } else { + child = Component.text("Run ", NamedTextColor.DARK_RED) + .append(Component.text(command, NamedTextColor.GRAY)) + .append(Component.text(" to reload the Discord connection")); + } - sender.sendMessage( - Component.text() - .append(Component.text("Some changes require a Discord connection reload. ", NamedTextColor.GRAY)) - .append(child) - ); + sender.sendMessage( + Component.text() + .append(Component.text("Some changes require a Discord connection reload. ", NamedTextColor.GRAY)) + .append(child) + ); + break; + } } } } - private Set getFlagsFromArguments(ICommandSender sender, GameCommandArguments arguments, AtomicBoolean dangerousFlags) { + private Set getFlagsFromArguments(ICommandSender sender, GameCommandArguments arguments, AtomicBoolean dangerousFlags) { String argument = null; try { argument = arguments.get("flags", String.class); @@ -136,16 +151,16 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester List parts = new ArrayList<>(Arrays.asList(argument.split(" "))); boolean confirm = parts.remove("-confirm"); - Set flags = new LinkedHashSet<>(); + Set flags = new LinkedHashSet<>(); if (discordSRV.status().isStartupError()) { // If startup error, use all flags parts.clear(); - flags.addAll(DiscordSRVApi.ReloadFlag.ALL); + flags.addAll(ReloadFlag.ALL); } for (String part : parts) { try { - DiscordSRV.ReloadFlag flag = DiscordSRV.ReloadFlag.valueOf(part.toUpperCase(Locale.ROOT)); + ReloadFlag flag = ReloadFlag.valueOf(part.toUpperCase(Locale.ROOT)); if (flag.requiresConfirm(discordSRV) && !confirm) { dangerousFlags.set(true); sender.sendMessage( @@ -175,7 +190,7 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester String last = currentInput.substring(lastSpace); String beforeLastSpace = currentInput.substring(0, lastSpace); - List options = DiscordSRV.ReloadFlag.ALL.stream() + List options = ReloadFlag.ALL.stream() .map(flag -> flag.name().toLowerCase(Locale.ROOT)) .filter(flag -> flag.startsWith(last)) .collect(Collectors.toList()); diff --git a/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadResults.java b/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadResults.java deleted file mode 100644 index a8e4c31b..00000000 --- a/common/src/main/java/com/discordsrv/common/command/game/commands/subcommand/reload/ReloadResults.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of DiscordSRV, licensed under the GPLv3 License - * Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.discordsrv.common.command.game.commands.subcommand.reload; - -import com.discordsrv.api.DiscordSRVApi; - -public enum ReloadResults implements DiscordSRVApi.ReloadResult { - FAILED, - DEFAULT_BOT_TOKEN, - SUCCESS, - SECURITY_FAILED, - STORAGE_CONNECTION_FAILED, - DISCORD_CONNECTION_RELOAD_REQUIRED, - DISCORD_CONNECTION_FAILED -} diff --git a/common/src/main/java/com/discordsrv/common/core/module/ModuleManager.java b/common/src/main/java/com/discordsrv/common/core/module/ModuleManager.java index ff36d981..8372d711 100644 --- a/common/src/main/java/com/discordsrv/common/core/module/ModuleManager.java +++ b/common/src/main/java/com/discordsrv/common/core/module/ModuleManager.java @@ -18,7 +18,6 @@ package com.discordsrv.common.core.module; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.connection.details.DiscordCacheFlag; import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent; import com.discordsrv.api.discord.connection.details.DiscordMemberCachePolicy; @@ -27,8 +26,8 @@ import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.events.lifecycle.DiscordSRVReadyEvent; import com.discordsrv.api.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.module.Module; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; -import com.discordsrv.common.command.game.commands.subcommand.reload.ReloadResults; import com.discordsrv.common.core.logging.Logger; import com.discordsrv.common.core.logging.NamedLogger; import com.discordsrv.common.core.module.type.AbstractModule; @@ -163,7 +162,7 @@ public class ModuleManager { logger.debug(module.getClass().getName() + " unregistered"); } - private List enable(AbstractModule module) { + private List enable(AbstractModule module) { try { if (module.enableModule()) { logger.debug(module + " enabled"); @@ -176,8 +175,8 @@ public class ModuleManager { return null; } - private List reload(AbstractModule module) { - List reloadResults = new ArrayList<>(); + private List reload(AbstractModule module) { + List reloadResults = new ArrayList<>(); try { module.reload(result -> { if (result == null) { @@ -225,7 +224,7 @@ public class ModuleManager { } } - public List reload() { + public List reload() { return reloadAndEnableModules(true); } @@ -233,19 +232,19 @@ public class ModuleManager { reloadAndEnableModules(false); } - private synchronized List reloadAndEnableModules(boolean reload) { + private synchronized List reloadAndEnableModules(boolean reload) { boolean isReady = discordSRV.isReady(); logger().debug((reload ? "Reloading" : "Enabling") + " modules (DiscordSRV ready = " + isReady + ")"); - Set reloadResults = new HashSet<>(); + Set reloadResults = new HashSet<>(); for (Module module : modules) { reloadResults.addAll(enableOrDisableAsNeeded(getAbstract(module), isReady, reload)); } - List results = new ArrayList<>(); + List results = new ArrayList<>(); - List validResults = Arrays.asList(DiscordSRVApi.ReloadResult.DefaultConstants.values()); - for (DiscordSRVApi.ReloadResult reloadResult : reloadResults) { + List validResults = Arrays.asList(ReloadResult.values()); + for (ReloadResult reloadResult : reloadResults) { if (validResults.contains(reloadResult)) { results.add(reloadResult); } @@ -254,7 +253,7 @@ public class ModuleManager { return results; } - private List enableOrDisableAsNeeded(AbstractModule module, boolean isReady, boolean mayReload) { + private List enableOrDisableAsNeeded(AbstractModule module, boolean isReady, boolean mayReload) { boolean canBeEnabled = isReady || module.canEnableBeforeReady(); if (!canBeEnabled) { return Collections.emptyList(); @@ -282,14 +281,14 @@ public class ModuleManager { } } - List reloadResults = new ArrayList<>(); + List reloadResults = new ArrayList<>(); if (fail) { - reloadResults.add(ReloadResults.DISCORD_CONNECTION_RELOAD_REQUIRED); + reloadResults.add(ReloadResult.DISCORD_CONNECTION_RELOAD_REQUIRED); } // Enable the module if reload passed if (!fail) { - List results = enable(module); + List results = enable(module); if (results != null) { reloadResults.addAll(results); } else if (mayReload) { diff --git a/common/src/main/java/com/discordsrv/common/core/module/type/ModuleDelegate.java b/common/src/main/java/com/discordsrv/common/core/module/type/ModuleDelegate.java index 6d7c3747..b1be744b 100644 --- a/common/src/main/java/com/discordsrv/common/core/module/type/ModuleDelegate.java +++ b/common/src/main/java/com/discordsrv/common/core/module/type/ModuleDelegate.java @@ -18,11 +18,11 @@ package com.discordsrv.common.core.module.type; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.connection.details.DiscordCacheFlag; import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent; import com.discordsrv.api.discord.connection.details.DiscordMemberCachePolicy; import com.discordsrv.api.module.Module; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.core.logging.NamedLogger; import org.jetbrains.annotations.NotNull; @@ -79,7 +79,7 @@ public class ModuleDelegate extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { module.reload(resultConsumer); } diff --git a/common/src/main/java/com/discordsrv/common/discord/connection/DiscordConnectionManager.java b/common/src/main/java/com/discordsrv/common/discord/connection/DiscordConnectionManager.java index 356e71e4..329392ee 100644 --- a/common/src/main/java/com/discordsrv/common/discord/connection/DiscordConnectionManager.java +++ b/common/src/main/java/com/discordsrv/common/discord/connection/DiscordConnectionManager.java @@ -21,15 +21,12 @@ package com.discordsrv.common.discord.connection; import net.dv8tion.jda.api.JDA; import org.jetbrains.annotations.Nullable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - public interface DiscordConnectionManager { /** * The default amount of milliseconds to wait for shutdown before ending without completing rate limited requests. */ - long DEFAULT_SHUTDOWN_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + int DEFAULT_SHUTDOWN_TIMEOUT = 10; /** * Gets the instance. @@ -40,33 +37,16 @@ public interface DiscordConnectionManager { /** * Attempts to connect to Discord. - * @return a {@link CompletableFuture} */ - CompletableFuture connect(); - - /** - * Shuts down the Discord connection and connects again. - * @return a {@link CompletableFuture} - */ - CompletableFuture reconnect(); - - /** - * Shuts down the Discord connection after waiting for queued requests to complete. Blocks until shutdown is completed. - * @return a {@link CompletableFuture} - * @see #DEFAULT_SHUTDOWN_TIMEOUT - */ - default CompletableFuture shutdown() { - return shutdown(DEFAULT_SHUTDOWN_TIMEOUT); - } + void connect(); /** * Shuts down the Discord connection after waiting for queued requests to complete. * Waits the provided amount of milliseconds before running {@link #shutdownNow()}. * - * @param timeoutMillis the maximum amount of milliseconds to wait for shut down - * @return a {@link CompletableFuture} + * @param timeoutSeconds the maximum amount of seconds to wait for JDA to shut down */ - CompletableFuture shutdown(long timeoutMillis); + void shutdown(int timeoutSeconds); /** * Shuts down the Discord connection without waiting for queued requests to be completed. diff --git a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java index d9891189..51d2d5ac 100644 --- a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java +++ b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java @@ -85,7 +85,6 @@ public class JDAConnectionManager implements DiscordConnectionManager { private ScheduledExecutorService rateLimitSchedulerPool; private ExecutorService rateLimitElasticPool; - private CompletableFuture connectionFuture; private JDA instance; // Currently used intents & cache flags @@ -286,20 +285,11 @@ public class JDAConnectionManager implements DiscordConnectionManager { // @Override - public CompletableFuture connect() { - if (connectionFuture != null && !connectionFuture.isDone()) { - throw new IllegalStateException("Already connecting"); - } else if (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN) { + public void connect() { + if (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN) { throw new IllegalStateException("Cannot reconnect, still active"); } - this.connectInternal(); - return CompletableFuture.completedFuture(null); - // TODO: investigate why this is broken - //return connectionFuture = discordSRV.scheduler().execute(this::connectInternal); - } - - private void connectInternal() { BotConfig botConfig = discordSRV.connectionConfig().bot; String token = botConfig.token; boolean defaultToken = false; @@ -412,8 +402,8 @@ public class JDAConnectionManager implements DiscordConnectionManager { // Our own (named) threads jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool()); jdaBuilder.setGatewayPool(gatewayPool); - jdaBuilder.setRateLimitScheduler(rateLimitSchedulerPool, true); - jdaBuilder.setRateLimitElastic(rateLimitElasticPool); + jdaBuilder.setRateLimitScheduler(rateLimitSchedulerPool); + jdaBuilder.setRateLimitElastic(rateLimitElasticPool, true); jdaBuilder.setHttpClient(discordSRV.httpClient()); WebSocketFactory webSocketFactory = new WebSocketFactory(); @@ -428,27 +418,13 @@ public class JDAConnectionManager implements DiscordConnectionManager { } } - @Override - public CompletableFuture reconnect() { - return discordSRV.scheduler().execute(() -> { - shutdown().join(); - connect().join(); - }); - } - @Subscribe(priority = EventPriority.LATE) public void onDSRVShuttingDown(DiscordSRVShuttingDownEvent event) { - // This has a timeout - shutdown().join(); + shutdown(DEFAULT_SHUTDOWN_TIMEOUT); } - @Override - public CompletableFuture shutdown(long timeoutMillis) { - return discordSRV.scheduler().execute(() -> shutdownInternal(timeoutMillis)); - } - - @SuppressWarnings("BusyWait") - private void shutdownInternal(long timeoutMillis) { + @SuppressWarnings("BusyWait") // Known + public void shutdown(int timeoutSeconds) { if (instance == null) { shutdownExecutors(); return; @@ -457,14 +433,16 @@ public class JDAConnectionManager implements DiscordConnectionManager { instance.shutdown(); try { - discordSRV.logger().info("Waiting up to " + TimeUnit.MILLISECONDS.toSeconds(timeoutMillis) + " seconds for JDA to shutdown..."); + discordSRV.logger().info("Waiting up to " + timeoutSeconds + " seconds for JDA to shutdown..."); discordSRV.scheduler().run(() -> { try { - while (instance != null && !rateLimitSchedulerPool.isShutdown()) { + while (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN && !rateLimitElasticPool.isShutdown()) { Thread.sleep(50); } - } catch (InterruptedException ignored) {} - }).get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + }).get(timeoutSeconds, TimeUnit.SECONDS); instance = null; shutdownExecutors(); discordSRV.logger().info("JDA shutdown completed."); @@ -478,7 +456,9 @@ public class JDAConnectionManager implements DiscordConnectionManager { } discordSRV.logger().error("Failed to shutdown JDA", t); } - } catch (InterruptedException ignored) {} + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } } @Override @@ -495,10 +475,10 @@ public class JDAConnectionManager implements DiscordConnectionManager { if (gatewayPool != null) { gatewayPool.shutdownNow(); } - if (rateLimitSchedulerPool != null && !rateLimitSchedulerPool.isShutdown()) { + if (rateLimitSchedulerPool != null) { rateLimitSchedulerPool.shutdownNow(); } - if (rateLimitElasticPool != null) { + if (rateLimitElasticPool != null && !rateLimitElasticPool.isShutdown()) { rateLimitElasticPool.shutdownNow(); } if (failureCallbackFuture != null) { diff --git a/common/src/main/java/com/discordsrv/common/feature/DiscordInviteModule.java b/common/src/main/java/com/discordsrv/common/feature/DiscordInviteModule.java index 6a141815..d2fdd341 100644 --- a/common/src/main/java/com/discordsrv/common/feature/DiscordInviteModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/DiscordInviteModule.java @@ -18,12 +18,12 @@ package com.discordsrv.common.feature; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent; import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext; import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.format.FormattedText; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.main.DiscordInviteConfig; import com.discordsrv.common.core.logging.NamedLogger; @@ -79,7 +79,7 @@ public class DiscordInviteModule extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { JDA jda = discordSRV.jda(); if (jda == null) { logger().debug("JDA == null"); diff --git a/common/src/main/java/com/discordsrv/common/feature/PresenceUpdaterModule.java b/common/src/main/java/com/discordsrv/common/feature/PresenceUpdaterModule.java index 65e31edc..258d370b 100644 --- a/common/src/main/java/com/discordsrv/common/feature/PresenceUpdaterModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/PresenceUpdaterModule.java @@ -18,10 +18,10 @@ package com.discordsrv.common.feature; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.eventbus.EventPriority; import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.events.lifecycle.DiscordSRVShuttingDownEvent; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.main.PresenceUpdaterConfig; import com.discordsrv.common.core.logging.NamedLogger; @@ -70,7 +70,7 @@ public class PresenceUpdaterModule extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { if (discordSRV.jda() == null) { return; } diff --git a/common/src/main/java/com/discordsrv/common/feature/channel/TimedUpdaterModule.java b/common/src/main/java/com/discordsrv/common/feature/channel/TimedUpdaterModule.java index 4979c740..d347845d 100644 --- a/common/src/main/java/com/discordsrv/common/feature/channel/TimedUpdaterModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/channel/TimedUpdaterModule.java @@ -18,8 +18,8 @@ package com.discordsrv.common.feature.channel; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.main.TimedUpdaterConfig; import com.discordsrv.common.core.logging.NamedLogger; @@ -67,7 +67,7 @@ public class TimedUpdaterModule extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { futures.forEach(future -> future.cancel(false)); futures.clear(); diff --git a/common/src/main/java/com/discordsrv/common/feature/console/ConsoleModule.java b/common/src/main/java/com/discordsrv/common/feature/console/ConsoleModule.java index bec7f07a..cf06ca7d 100644 --- a/common/src/main/java/com/discordsrv/common/feature/console/ConsoleModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/console/ConsoleModule.java @@ -18,10 +18,10 @@ package com.discordsrv.common.feature.console; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent; import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.events.discord.message.DiscordMessageReceiveEvent; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.main.ConsoleConfig; import com.discordsrv.common.config.main.generic.DestinationConfig; @@ -70,7 +70,7 @@ public class ConsoleModule extends AbstractModule implements LogAppe } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { List configs = discordSRV.config().console; Set uncheckedConfigs = new LinkedHashSet<>(configs); diff --git a/common/src/main/java/com/discordsrv/common/feature/customcommands/CustomCommandModule.java b/common/src/main/java/com/discordsrv/common/feature/customcommands/CustomCommandModule.java index 6eb9c0aa..d893f7a0 100644 --- a/common/src/main/java/com/discordsrv/common/feature/customcommands/CustomCommandModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/customcommands/CustomCommandModule.java @@ -18,7 +18,6 @@ package com.discordsrv.common.feature.customcommands; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.discord.entity.DiscordUser; import com.discordsrv.api.discord.entity.channel.DiscordChannel; import com.discordsrv.api.discord.entity.guild.DiscordGuildMember; @@ -28,6 +27,7 @@ import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand; import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier; import com.discordsrv.api.discord.entity.message.SendableDiscordMessage; import com.discordsrv.api.events.discord.interaction.command.AbstractCommandInteractionEvent; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.main.CustomCommandConfig; import com.discordsrv.common.core.logging.NamedLogger; @@ -55,7 +55,7 @@ public class CustomCommandModule extends AbstractModule { } @Override - public void reload(Consumer resultConsumer) { + public void reload(Consumer resultConsumer) { List configs = discordSRV.config().customCommands; List layeredCommands = new ArrayList<>(); diff --git a/common/src/main/java/com/discordsrv/common/feature/linking/requirelinking/RequiredLinkingModule.java b/common/src/main/java/com/discordsrv/common/feature/linking/requirelinking/RequiredLinkingModule.java index 06af6f2c..998cff11 100644 --- a/common/src/main/java/com/discordsrv/common/feature/linking/requirelinking/RequiredLinkingModule.java +++ b/common/src/main/java/com/discordsrv/common/feature/linking/requirelinking/RequiredLinkingModule.java @@ -18,9 +18,9 @@ package com.discordsrv.common.feature.linking.requirelinking; -import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.eventbus.Subscribe; import com.discordsrv.api.events.linking.AccountUnlinkedEvent; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.abstraction.player.IPlayer; import com.discordsrv.common.config.main.linking.RequiredLinkingConfig; @@ -101,7 +101,7 @@ public abstract class RequiredLinkingModule extends Abstra } @Override - public final void reload(Consumer resultConsumer) { + public final void reload(Consumer resultConsumer) { List> requirementTypes = new ArrayList<>(); requirementTypes.add(new DiscordRoleRequirementType(this));