From 774e7402074a0f98d16d92d0a571b0b6f8f1db05 Mon Sep 17 00:00:00 2001 From: RaftDev Date: Sat, 2 Dec 2023 13:48:52 +0100 Subject: [PATCH] Clean up when module deactivates or crashes --- .../network/api/AtlasNetworkServer.java | 7 +-- .../atlasworld/network/api/NetworkModule.java | 11 +++++ .../api/concurrent/action/FutureAction.java | 6 +++ .../concurrent/action/SimpleFutureAction.java | 46 ++++++++++++++++--- .../atlasworld/network/api/module/Module.java | 11 +++++ .../network/api/services/ServiceManager.java | 4 +- .../network/api/test/TestModule.java | 2 +- .../atlasworld/network/core/AtlasNetwork.java | 19 +++++--- .../concurrent/action/ActionUtilities.java | 6 +-- .../network/core/console/SystemConsole.java | 2 + .../core/services/SystemServiceManager.java | 4 +- 11 files changed, 97 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/fr/atlasworld/network/api/AtlasNetworkServer.java b/api/src/main/java/fr/atlasworld/network/api/AtlasNetworkServer.java index 5da7183..dab80b9 100644 --- a/api/src/main/java/fr/atlasworld/network/api/AtlasNetworkServer.java +++ b/api/src/main/java/fr/atlasworld/network/api/AtlasNetworkServer.java @@ -4,6 +4,7 @@ import fr.atlasworld.network.api.command.CommandManager; import fr.atlasworld.network.api.concurrent.action.FutureAction; import fr.atlasworld.network.api.file.FileManager; +import fr.atlasworld.network.api.module.Module; import fr.atlasworld.network.api.networking.Socket; import fr.atlasworld.network.api.security.SecurityManager; import fr.atlasworld.network.api.services.ServiceManager; @@ -60,7 +61,7 @@ public interface AtlasNetworkServer { * @return future action to notify your code when your task finishes, of fails. */ @CanIgnoreReturnValue - FutureAction createAsyncSupplier(Supplier supplier); + FutureAction createAsyncSupplier(Module module, Supplier supplier); /** * Creates an asynchronous task using the system's Executor Pool. @@ -68,7 +69,7 @@ public interface AtlasNetworkServer { * @return future action to notify your code when your task finishes, or fails. */ @CanIgnoreReturnValue - FutureAction createAsyncTask(Runnable runnable); + FutureAction createAsyncTask(Module module, Runnable runnable); /** * Creates an asynchronous task that will run every specified time interval. @@ -78,5 +79,5 @@ public interface AtlasNetworkServer { * @return future action, this action will never successfully complete, it can only fail or be cancelled. */ @CanIgnoreReturnValue - FutureAction createSequenceTask(Runnable runnable, long interval, TimeUnit intervalUnit); + FutureAction createSequenceTask(Module module, Runnable runnable, long interval, TimeUnit intervalUnit); } diff --git a/api/src/main/java/fr/atlasworld/network/api/NetworkModule.java b/api/src/main/java/fr/atlasworld/network/api/NetworkModule.java index 6ebc559..2d36131 100644 --- a/api/src/main/java/fr/atlasworld/network/api/NetworkModule.java +++ b/api/src/main/java/fr/atlasworld/network/api/NetworkModule.java @@ -1,5 +1,7 @@ package fr.atlasworld.network.api; +import fr.atlasworld.network.api.concurrent.action.FutureAction; +import fr.atlasworld.network.api.concurrent.action.SimpleFutureAction; import fr.atlasworld.network.api.loader.ModuleClassLoader; import fr.atlasworld.network.api.module.Module; import fr.atlasworld.network.api.module.ModuleMeta; @@ -30,6 +32,7 @@ public abstract class NetworkModule implements Module { private ModuleStatus status = ModuleStatus.INACTIVE; private Logger logger = null; private boolean loaded = false; + private SimpleFutureAction deactivationFuture; @ApiStatus.Internal public NetworkModule() { @@ -106,6 +109,7 @@ public final void activate(@NotNull ModuleActivationContext ctx) { if (this.status == ModuleStatus.ACTIVE) throw new IllegalStateException("Module is already activated!"); + this.deactivationFuture = new SimpleFutureAction<>(); this.status = ModuleStatus.ACTIVE; this.onActivate(ctx); } @@ -118,6 +122,7 @@ public final void deactivate(@NotNull ModuleDeactivationContext ctx) { this.status = ModuleStatus.INACTIVE; this.onDeactivate(ctx); + this.deactivationFuture.complete(null); // Trigger success listeners. } @Override @@ -125,12 +130,18 @@ public final void deactivate(@NotNull ModuleDeactivationContext ctx) { public final void crash(Throwable throwable) { this.status = ModuleStatus.CRASHED; this.logger.error("Oh no! I just crashed!", throwable); + this.deactivationFuture.complete(null); // Trigger success listeners. } public final Logger getLogger() { return this.logger; } + @Override + public final FutureAction deactivationFuture() { + return this.deactivationFuture; + } + @Override public final @Nullable InputStream getResource(@NotNull String resource) { if (this.archive == null) diff --git a/api/src/main/java/fr/atlasworld/network/api/concurrent/action/FutureAction.java b/api/src/main/java/fr/atlasworld/network/api/concurrent/action/FutureAction.java index 8d1e28c..6423ad8 100644 --- a/api/src/main/java/fr/atlasworld/network/api/concurrent/action/FutureAction.java +++ b/api/src/main/java/fr/atlasworld/network/api/concurrent/action/FutureAction.java @@ -5,6 +5,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -23,6 +24,11 @@ public interface FutureAction extends Future { */ FutureAction onFailure(@NotNull Consumer listener); + /** + * Execute code when the action is done, whether is succeeded or failed. + */ + FutureAction whenDone(BiConsumer listener); + /** * Check if action was successful. * @return true if the action is done and was successful. diff --git a/api/src/main/java/fr/atlasworld/network/api/concurrent/action/SimpleFutureAction.java b/api/src/main/java/fr/atlasworld/network/api/concurrent/action/SimpleFutureAction.java index 2628621..2e96278 100644 --- a/api/src/main/java/fr/atlasworld/network/api/concurrent/action/SimpleFutureAction.java +++ b/api/src/main/java/fr/atlasworld/network/api/concurrent/action/SimpleFutureAction.java @@ -1,21 +1,26 @@ package fr.atlasworld.network.api.concurrent.action; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; public class SimpleFutureAction implements FutureAction { private final CountDownLatch latch = new CountDownLatch(1); - private Consumer onSuccess = v -> {}; - private Consumer onFailure = cause -> {}; + private final List> onSuccess; + private final List> onFailure; + private final List> whenDone; private Function onCancellation = interrupt -> false; private boolean done = false; @@ -25,15 +30,29 @@ public class SimpleFutureAction implements FutureAction { private V value; private Throwable cause; + public SimpleFutureAction() { + this.onSuccess = new ArrayList<>(); + this.onFailure = new ArrayList<>(); + this.whenDone = new ArrayList<>(); + } + @Override + @CanIgnoreReturnValue public SimpleFutureAction onSuccess(@NotNull Consumer listener) { - this.onSuccess = listener; + this.onSuccess.add(listener); return this; } @Override + @CanIgnoreReturnValue public SimpleFutureAction onFailure(@NotNull Consumer listener) { - this.onFailure = listener; + this.onFailure.add(listener); + return this; + } + + @Override + public FutureAction whenDone(BiConsumer listener) { + this.whenDone.add(listener); return this; } @@ -68,6 +87,7 @@ public V syncUninterruptibly() { } } + @CanIgnoreReturnValue public SimpleFutureAction onCancellation(Function listener) { this.onCancellation = listener; return this; @@ -112,7 +132,14 @@ public void complete(V value) { this.success = true; this.latch.countDown(); - this.onSuccess.accept(this.value); + + for (Consumer listener : this.onSuccess) { + listener.accept(this.value); + } + + for (BiConsumer listener : this.whenDone) { + listener.accept(this.value, this.cause); + } } public void fail(Throwable cause) { @@ -124,6 +151,13 @@ public void fail(Throwable cause) { this.done = true; this.latch.countDown(); - this.onFailure.accept(this.cause); + + for (Consumer listener : this.onFailure) { + listener.accept(this.cause); + } + + for (BiConsumer listener : this.whenDone) { + listener.accept(this.value, this.cause); + } } } diff --git a/api/src/main/java/fr/atlasworld/network/api/module/Module.java b/api/src/main/java/fr/atlasworld/network/api/module/Module.java index f77b9c5..7de21b0 100644 --- a/api/src/main/java/fr/atlasworld/network/api/module/Module.java +++ b/api/src/main/java/fr/atlasworld/network/api/module/Module.java @@ -1,11 +1,13 @@ package fr.atlasworld.network.api.module; +import fr.atlasworld.network.api.concurrent.action.FutureAction; import fr.atlasworld.network.api.module.lifecycle.ModuleActivationContext; import fr.atlasworld.network.api.module.lifecycle.ModuleDeactivationContext; import fr.atlasworld.network.api.module.lifecycle.ModuleLoadContext; import fr.atlasworld.network.api.util.archive.Archive; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; @@ -60,4 +62,13 @@ public interface Module extends Archive { */ @ApiStatus.Internal void crash(Throwable throwable); + + /** + * Retrieve the deactivation future of the module. + * Success listeners are called when the module deactivates normally. + * Fail listeners are called when the module crashes. + * @return null if the module is not yet activated. + */ + @Nullable + FutureAction deactivationFuture(); } diff --git a/api/src/main/java/fr/atlasworld/network/api/services/ServiceManager.java b/api/src/main/java/fr/atlasworld/network/api/services/ServiceManager.java index b3a2828..217cd04 100644 --- a/api/src/main/java/fr/atlasworld/network/api/services/ServiceManager.java +++ b/api/src/main/java/fr/atlasworld/network/api/services/ServiceManager.java @@ -1,5 +1,6 @@ package fr.atlasworld.network.api.services; +import fr.atlasworld.network.api.module.Module; import org.jetbrains.annotations.Nullable; /** @@ -9,10 +10,11 @@ public interface ServiceManager { /** * Register a service implementation to the service manager. + * @param module module of the service class. * @param serviceClass parent service class. * @param serviceImpl implementation of the service class. */ - void registerService(Class serviceClass, T serviceImpl); + void registerService(Module module, Class serviceClass, T serviceImpl); /** * Get a service from the service manager. diff --git a/api/src/test/java/fr/atlasworld/network/api/test/TestModule.java b/api/src/test/java/fr/atlasworld/network/api/test/TestModule.java index f401ccf..298c2f1 100644 --- a/api/src/test/java/fr/atlasworld/network/api/test/TestModule.java +++ b/api/src/test/java/fr/atlasworld/network/api/test/TestModule.java @@ -38,7 +38,7 @@ protected void onActivate(@NotNull ModuleActivationContext ctx) { LOGGER.info("Registering services.."); ctx.getServer().getServiceManager().registerService(DatabaseService.class, new LocalDatabaseService()); - ctx.getServer().createSequenceTask(() -> { + ctx.getServer().createSequenceTask(this, () -> { LOGGER.info("Hi!"); }, 10, TimeUnit.SECONDS); diff --git a/src/main/java/fr/atlasworld/network/core/AtlasNetwork.java b/src/main/java/fr/atlasworld/network/core/AtlasNetwork.java index f781fad..b7a96c7 100644 --- a/src/main/java/fr/atlasworld/network/core/AtlasNetwork.java +++ b/src/main/java/fr/atlasworld/network/core/AtlasNetwork.java @@ -5,6 +5,7 @@ import fr.atlasworld.network.api.command.CommandManager; import fr.atlasworld.network.api.concurrent.action.FutureAction; import fr.atlasworld.network.api.file.FileManager; +import fr.atlasworld.network.api.module.Module; import fr.atlasworld.network.api.networking.Socket; import fr.atlasworld.network.api.security.SecurityManager; import fr.atlasworld.network.api.services.ServiceManager; @@ -170,18 +171,24 @@ public Socket getSocket() { } @Override - public FutureAction createAsyncSupplier(Supplier supplier) { - return ActionUtilities.supply(supplier); + public FutureAction createAsyncSupplier(Module module, Supplier supplier) { + SimpleFutureAction action = ActionUtilities.supply(supplier); + module.deactivationFuture().whenDone((v, cause) -> action.cancel(cause != null)); // Interrupt if the module crashed. + return action; } @Override - public FutureAction createAsyncTask(Runnable runnable) { - return ActionUtilities.execute(runnable); + public FutureAction createAsyncTask(Module module, Runnable runnable) { + SimpleFutureAction action = ActionUtilities.execute(runnable); + module.deactivationFuture().whenDone((v, cause) -> action.cancel(cause != null)); // Interrupt if the module crashed. + return action; } @Override - public FutureAction createSequenceTask(Runnable runnable, long interval, TimeUnit intervalUnit) { - return ActionUtilities.sequential(runnable, interval, intervalUnit); + public FutureAction createSequenceTask(Module module, Runnable runnable, long interval, TimeUnit intervalUnit) { + SimpleFutureAction action = ActionUtilities.sequential(runnable, interval, intervalUnit); + module.deactivationFuture().whenDone((v, cause) -> action.cancel(cause != null)); // Interrupt if the module crashed. + return action; } // Static fields diff --git a/src/main/java/fr/atlasworld/network/core/concurrent/action/ActionUtilities.java b/src/main/java/fr/atlasworld/network/core/concurrent/action/ActionUtilities.java index c655f1d..b4d1119 100644 --- a/src/main/java/fr/atlasworld/network/core/concurrent/action/ActionUtilities.java +++ b/src/main/java/fr/atlasworld/network/core/concurrent/action/ActionUtilities.java @@ -9,7 +9,7 @@ import java.util.function.Supplier; public class ActionUtilities { - public static FutureAction supply(Supplier supplier) { + public static SimpleFutureAction supply(Supplier supplier) { SimpleFutureAction action = new SimpleFutureAction<>(); Environment.SYSTEM_EXECUTOR.execute(() -> { @@ -23,7 +23,7 @@ public static FutureAction supply(Supplier supplier) { return action; } - public static FutureAction execute(Runnable runnable) { + public static SimpleFutureAction execute(Runnable runnable) { SimpleFutureAction action = new SimpleFutureAction<>(); Environment.SYSTEM_EXECUTOR.execute(() -> { @@ -38,7 +38,7 @@ public static FutureAction execute(Runnable runnable) { return action; } - public static FutureAction sequential(Runnable runnable, long timeInterval, TimeUnit unit) { + public static SimpleFutureAction sequential(Runnable runnable, long timeInterval, TimeUnit unit) { SimpleFutureAction action = new SimpleFutureAction<>(); ScheduledFuture future = Environment.SYSTEM_EXECUTOR.scheduleAtFixedRate(() -> { diff --git a/src/main/java/fr/atlasworld/network/core/console/SystemConsole.java b/src/main/java/fr/atlasworld/network/core/console/SystemConsole.java index 4697c5d..65c62e1 100644 --- a/src/main/java/fr/atlasworld/network/core/console/SystemConsole.java +++ b/src/main/java/fr/atlasworld/network/core/console/SystemConsole.java @@ -98,6 +98,8 @@ public void register(Module module, Consumer> storeB this.commands.putIfAbsent(module, new ArrayList<>()); this.commands.get(module).addAll(store.getRegisteredCommands()); + + module.deactivationFuture().whenDone((v, cause) -> this.unregister(module)); // Unregister commands when the module deactivates. } @Override diff --git a/src/main/java/fr/atlasworld/network/core/services/SystemServiceManager.java b/src/main/java/fr/atlasworld/network/core/services/SystemServiceManager.java index a5b7cf5..bd5b4e0 100644 --- a/src/main/java/fr/atlasworld/network/core/services/SystemServiceManager.java +++ b/src/main/java/fr/atlasworld/network/core/services/SystemServiceManager.java @@ -1,5 +1,6 @@ package fr.atlasworld.network.core.services; +import fr.atlasworld.network.api.module.Module; import fr.atlasworld.network.api.services.Service; import fr.atlasworld.network.api.services.ServiceManager; import org.jetbrains.annotations.Nullable; @@ -20,8 +21,9 @@ public SystemServiceManager() { } @Override - public void registerService(Class serviceClass, T serviceImpl) { + public void registerService(Module module, Class serviceClass, T serviceImpl) { this.registeredServices.put(serviceClass, serviceImpl); + module.deactivationFuture().whenDone((v, cause) -> this.registeredServices.remove(serviceClass)); // Unregister services when the module deactivates. } @Override