Preload dependencies in Docker image

This commit is contained in:
Luck 2022-10-22 00:07:40 +01:00
parent dc22847fa3
commit 5ff9a12a72
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
6 changed files with 129 additions and 27 deletions

View File

@ -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<Dependency, Path> 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<StorageType> storageTypes) {
loadDependencies(this.registry.resolveStorageDependencies(storageTypes));
public void loadStorageDependencies(Set<StorageType> storageTypes, boolean redis, boolean rabbitmq) {
loadDependencies(this.registry.resolveStorageDependencies(storageTypes, redis, rabbitmq));
}
public void loadDependencies(Set<Dependency> 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);
}
}

View File

@ -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<Dependency> resolveStorageDependencies(Set<StorageType> storageTypes) {
public Set<Dependency> resolveStorageDependencies(Set<StorageType> storageTypes, boolean redis, boolean rabbitmq) {
Set<Dependency> 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<Relocation> 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"));
}
}

View File

@ -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<StorageType> 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();

View File

@ -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 \

View File

@ -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();

View File

@ -0,0 +1,69 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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<Dependency> 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!");
}
}