From 5ff9a12a727432f32efb3978782ea3d48449687d Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 22 Oct 2022 00:07:40 +0100 Subject: [PATCH] Preload dependencies in Docker image --- .../dependencies/DependencyManager.java | 39 ++++++++--- .../dependencies/DependencyRegistry.java | 20 +++--- .../plugin/AbstractLuckPermsPlugin.java | 8 ++- standalone/docker/Dockerfile | 3 + .../standalone/loader/StandaloneLoader.java | 17 ++++- .../StandaloneDependencyPreloader.java | 69 +++++++++++++++++++ 6 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneDependencyPreloader.java diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java index 696ec8094..115ac2853 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java @@ -31,9 +31,12 @@ import me.lucko.luckperms.common.dependencies.classloader.IsolatedClassLoader; import me.lucko.luckperms.common.dependencies.relocation.Relocation; import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.plugin.classpath.ClassPathAppender; import me.lucko.luckperms.common.storage.StorageType; import me.lucko.luckperms.common.util.MoreFiles; +import net.luckperms.api.platform.Platform; + import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import java.io.IOException; @@ -49,18 +52,21 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; /** * Loads and manages runtime dependencies for the plugin. */ public class DependencyManager { - /** The plugin instance */ - private final LuckPermsPlugin plugin; /** A registry containing plugin specific behaviour for dependencies. */ private final DependencyRegistry registry; /** The path where library jars are cached. */ private final Path cacheDirectory; + /** The classpath appender to preload dependencies into */ + private final ClassPathAppender classPathAppender; + /** The executor to use when loading dependencies */ + private final Executor loadingExecutor; /** A map of dependencies which have already been loaded. */ private final EnumMap loaded = new EnumMap<>(Dependency.class); @@ -70,9 +76,17 @@ public class DependencyManager { private @MonotonicNonNull RelocationHandler relocationHandler = null; public DependencyManager(LuckPermsPlugin plugin) { - this.plugin = plugin; - this.registry = new DependencyRegistry(plugin); + this.registry = new DependencyRegistry(plugin.getBootstrap().getType()); this.cacheDirectory = setupCacheDirectory(plugin); + this.classPathAppender = plugin.getBootstrap().getClassPathAppender(); + this.loadingExecutor = plugin.getBootstrap().getScheduler().async(); + } + + public DependencyManager(Path cacheDirectory, Executor executor) { // standalone + this.registry = new DependencyRegistry(Platform.Type.STANDALONE); + this.cacheDirectory = cacheDirectory; + this.classPathAppender = null; + this.loadingExecutor = executor; } private synchronized RelocationHandler getRelocationHandler() { @@ -114,19 +128,24 @@ public class DependencyManager { } } - public void loadStorageDependencies(Set storageTypes) { - loadDependencies(this.registry.resolveStorageDependencies(storageTypes)); + public void loadStorageDependencies(Set storageTypes, boolean redis, boolean rabbitmq) { + loadDependencies(this.registry.resolveStorageDependencies(storageTypes, redis, rabbitmq)); } public void loadDependencies(Set dependencies) { CountDownLatch latch = new CountDownLatch(dependencies.size()); for (Dependency dependency : dependencies) { - this.plugin.getBootstrap().getScheduler().async().execute(() -> { + if (this.loaded.containsKey(dependency)) { + latch.countDown(); + continue; + } + + this.loadingExecutor.execute(() -> { try { loadDependency(dependency); } catch (Throwable e) { - this.plugin.getLogger().severe("Unable to load dependency " + dependency.name() + ".", e); + new RuntimeException("Unable to load dependency " + dependency.name(), e).printStackTrace(); } finally { latch.countDown(); } @@ -149,8 +168,8 @@ public class DependencyManager { this.loaded.put(dependency, file); - if (this.registry.shouldAutoLoad(dependency)) { - this.plugin.getBootstrap().getClassPathAppender().addJarToClasspath(file); + if (this.classPathAppender != null && this.registry.shouldAutoLoad(dependency)) { + this.classPathAppender.addJarToClasspath(file); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java index 189a6105a..bb0b5a28d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyRegistry.java @@ -30,10 +30,8 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.SetMultimap; import com.google.gson.JsonElement; -import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.dependencies.relocation.Relocation; import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler; -import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.storage.StorageType; import net.luckperms.api.platform.Platform; @@ -68,26 +66,26 @@ public class DependencyRegistry { Platform.Type.BUKKIT, Platform.Type.BUNGEECORD, Platform.Type.SPONGE, Platform.Type.NUKKIT ); - private final LuckPermsPlugin plugin; + private final Platform.Type platformType; - public DependencyRegistry(LuckPermsPlugin plugin) { - this.plugin = plugin; + public DependencyRegistry(Platform.Type platformType) { + this.platformType = platformType; } - public Set resolveStorageDependencies(Set storageTypes) { + public Set resolveStorageDependencies(Set storageTypes, boolean redis, boolean rabbitmq) { Set dependencies = new LinkedHashSet<>(); for (StorageType storageType : storageTypes) { dependencies.addAll(STORAGE_DEPENDENCIES.get(storageType)); } - if (this.plugin.getConfiguration().get(ConfigKeys.REDIS_ENABLED)) { + if (redis) { dependencies.add(Dependency.COMMONS_POOL_2); dependencies.add(Dependency.JEDIS); dependencies.add(Dependency.SLF4J_API); dependencies.add(Dependency.SLF4J_SIMPLE); } - if (this.plugin.getConfiguration().get(ConfigKeys.RABBITMQ_ENABLED)) { + if (rabbitmq) { dependencies.add(Dependency.RABBITMQ); } @@ -98,7 +96,7 @@ public class DependencyRegistry { } // don't load snakeyaml if it's provided by the platform - if (dependencies.contains(Dependency.SNAKEYAML) && SNAKEYAML_PROVIDED_BY_PLATFORM.contains(this.plugin.getBootstrap().getType())) { + if (dependencies.contains(Dependency.SNAKEYAML) && SNAKEYAML_PROVIDED_BY_PLATFORM.contains(this.platformType)) { dependencies.remove(Dependency.SNAKEYAML); } @@ -106,8 +104,6 @@ public class DependencyRegistry { } public void applyRelocationSettings(Dependency dependency, List relocations) { - Platform.Type type = this.plugin.getBootstrap().getType(); - // support for LuckPerms legacy (bukkit 1.7.10) if (!RelocationHandler.DEPENDENCIES.contains(dependency) && isGsonRelocated()) { relocations.add(Relocation.of("guava", "com{}google{}common")); @@ -115,7 +111,7 @@ public class DependencyRegistry { } // relocate yaml within configurate if its being provided by LP - if (dependency == Dependency.CONFIGURATE_YAML && !SNAKEYAML_PROVIDED_BY_PLATFORM.contains(type)) { + if (dependency == Dependency.CONFIGURATE_YAML && !SNAKEYAML_PROVIDED_BY_PLATFORM.contains(this.platformType)) { relocations.add(Relocation.of("yaml", "org{}yaml{}snakeyaml")); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java index 0650fcb47..efa2bc687 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java @@ -53,7 +53,6 @@ import me.lucko.luckperms.common.messaging.MessagingFactory; import me.lucko.luckperms.common.plugin.logging.PluginLogger; import me.lucko.luckperms.common.storage.Storage; import me.lucko.luckperms.common.storage.StorageFactory; -import me.lucko.luckperms.common.storage.StorageType; import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher; import me.lucko.luckperms.common.storage.misc.DataConstraints; import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; @@ -160,8 +159,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin { // now the configuration is loaded, we can create a storage factory and load initial dependencies StorageFactory storageFactory = new StorageFactory(this); - Set storageTypes = storageFactory.getRequiredTypes(); - this.dependencyManager.loadStorageDependencies(storageTypes); + this.dependencyManager.loadStorageDependencies( + storageFactory.getRequiredTypes(), + getConfiguration().get(ConfigKeys.REDIS_ENABLED), + getConfiguration().get(ConfigKeys.RABBITMQ_ENABLED) + ); // register listeners registerPlatformListeners(); diff --git a/standalone/docker/Dockerfile b/standalone/docker/Dockerfile index b3c7132be..320b645a2 100644 --- a/standalone/docker/Dockerfile +++ b/standalone/docker/Dockerfile @@ -19,6 +19,9 @@ RUN mv * luckperms-standalone.jar RUN mkdir data VOLUME ["/opt/luckperms/data"] +# preload and relocate dependency jars +RUN java -jar luckperms-standalone.jar preloadDependencies + CMD ["java", "-jar", "luckperms-standalone.jar", "--docker"] HEALTHCHECK --interval=30s --timeout=15s --start-period=20s \ diff --git a/standalone/loader/src/main/java/me/lucko/luckperms/standalone/loader/StandaloneLoader.java b/standalone/loader/src/main/java/me/lucko/luckperms/standalone/loader/StandaloneLoader.java index 5476a3282..2a384bd47 100644 --- a/standalone/loader/src/main/java/me/lucko/luckperms/standalone/loader/StandaloneLoader.java +++ b/standalone/loader/src/main/java/me/lucko/luckperms/standalone/loader/StandaloneLoader.java @@ -51,7 +51,8 @@ public class StandaloneLoader implements ShutdownCallback { public static final Logger LOGGER = LogManager.getLogger(StandaloneLoader.class); private static final String JAR_NAME = "luckperms-standalone.jarinjar"; - private static final String BOOTSTRAP_CLASS = "me.lucko.luckperms.standalone.LPStandaloneBootstrap"; + private static final String BOOTSTRAP_PLUGIN_CLASS = "me.lucko.luckperms.standalone.LPStandaloneBootstrap"; + private static final String BOOTSTRAP_DEPENDENCY_PRELOADER_CLASS = "me.lucko.luckperms.standalone.StandaloneDependencyPreloader"; private LuckPermsApplication app; private JarInJarClassLoader loader; @@ -72,7 +73,19 @@ public class StandaloneLoader implements ShutdownCallback { // create a jar-in-jar classloader for the standalone plugin, then enable it // the application is passes to the plugin constructor, to allow it to pass hooks back this.loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME); - this.plugin = this.loader.instantiatePlugin(BOOTSTRAP_CLASS, LuckPermsApplication.class, this.app); + + // special case for dependency preload command + if (args.length == 1 && args[0].equals("preloadDependencies")) { + try { + Class clazz = this.loader.loadClass(BOOTSTRAP_DEPENDENCY_PRELOADER_CLASS); + clazz.getMethod("main").invoke(null); + } catch (Exception e) { + e.printStackTrace(); + } + return; + } + + this.plugin = this.loader.instantiatePlugin(BOOTSTRAP_PLUGIN_CLASS, LuckPermsApplication.class, this.app); this.plugin.onLoad(); this.plugin.onEnable(); diff --git a/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneDependencyPreloader.java b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneDependencyPreloader.java new file mode 100644 index 000000000..c38976d18 --- /dev/null +++ b/standalone/src/main/java/me/lucko/luckperms/standalone/StandaloneDependencyPreloader.java @@ -0,0 +1,69 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) 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 me.lucko.luckperms.standalone; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import me.lucko.luckperms.common.dependencies.Dependency; +import me.lucko.luckperms.common.dependencies.DependencyManager; +import me.lucko.luckperms.common.dependencies.relocation.RelocationHandler; +import me.lucko.luckperms.common.util.MoreFiles; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Pre-loads and pre-relocates all possible dependencies. + */ +public class StandaloneDependencyPreloader { + + public static void main(String[] args) throws Exception { + main(); + } + + public static void main() throws Exception { + Path cacheDirectory = Paths.get("data").resolve("libs"); + MoreFiles.createDirectoriesIfNotExists(cacheDirectory); + + ExecutorService executorService = Executors.newFixedThreadPool(8, new ThreadFactoryBuilder().setDaemon(true).build()); + DependencyManager dependencyManager = new DependencyManager(cacheDirectory, executorService); + + Set dependencies = new HashSet<>(Arrays.asList(Dependency.values())); + System.out.println("Preloading " + dependencies.size() + " dependencies..."); + + dependencies.removeAll(RelocationHandler.DEPENDENCIES); + dependencyManager.loadDependencies(RelocationHandler.DEPENDENCIES); + dependencyManager.loadDependencies(dependencies); + + System.out.println("Done!"); + } +} +