Working extensions + extension dependencies without MSRC

The code is pretty messy, and external dependencies do not work (i dont think). MSEC instances now have a parent of MinecraftServer's CL and use the regular Java classloader search path (parent first). If the class cannot be found (its in a dependency), then it searches through its child classloaders (of which the dependency is one).
This commit is contained in:
Matt Worzala 2021-11-11 09:25:29 -05:00 committed by TheMode
parent 18ecefd97a
commit 7130cc9dc9
4 changed files with 116 additions and 70 deletions

View File

@ -12,8 +12,10 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
/** /**
* Represents an extension from an `extension.json` that is capable of powering an Extension object. * Represents an extension from an `extension.json` that is capable of powering an Extension object.
@ -21,6 +23,7 @@ import java.util.List;
* This has no constructor as its properties are set via GSON. * This has no constructor as its properties are set via GSON.
*/ */
public final class DiscoveredExtension { public final class DiscoveredExtension {
private static final ExtensionManager EXTENSION_MANAGER = MinecraftServer.getExtensionManager();
/** Static logger for this class. */ /** Static logger for this class. */
public static final Logger LOGGER = LoggerFactory.getLogger(DiscoveredExtension.class); public static final Logger LOGGER = LoggerFactory.getLogger(DiscoveredExtension.class);
@ -205,30 +208,47 @@ public final class DiscoveredExtension {
return meta; return meta;
} }
public MinestomExtensionClassLoader makeClassLoader() { public MinestomExtensionClassLoader makeClassLoader(List<DiscoveredExtension> discoveredExtensions) {
final URL[] urls = this.files.toArray(new URL[0]); final URL[] urls = this.files.toArray(new URL[0]);
MinestomRootClassLoader root = MinestomRootClassLoader.getInstance(); MinestomExtensionClassLoader loader = new MinestomExtensionClassLoader(this.getName(), this.getEntrypoint(), urls, MinecraftServer.class.getClassLoader());
MinestomExtensionClassLoader loader = new MinestomExtensionClassLoader(this.getName(), this.getEntrypoint(), urls, root); System.out.println("CREATED " + loader + " WITH " + Arrays.toString(urls));
if (this.getDependencies().length == 0 || MinecraftServer.getExtensionManager() == null) { // it also may invoked in early class loader // add children to the dependencies
// orphaned extension, we can insert it directly for (String dependencyName : this.getDependencies()) {
root.addChild(loader); DiscoveredExtension dependency = discoveredExtensions.stream()
} else { .filter(ext -> ext.getName().equalsIgnoreCase(dependencyName))
// add children to the dependencies .findFirst().orElse(null);
for (String dependency : this.getDependencies()) {
if (MinecraftServer.getExtensionManager().hasExtension(dependency.toLowerCase())) {
MinestomExtensionClassLoader parentLoader = MinecraftServer.getExtensionManager().getExtension(dependency.toLowerCase()).getOrigin().getMinestomExtensionClassLoader();
// TODO should never happen but replace with better throws error. if (dependency != null) {
assert parentLoader != null; MinestomExtensionClassLoader dependencyLoader = dependency.getMinestomExtensionClassLoader();
assert dependencyLoader != null; //TODO: Better error handling
parentLoader.addChild(loader); loader.addChild(dependencyLoader);
} } else {
//TODO: Better error handling
throw new RuntimeException("Missing dependency " + dependencyName);
} }
} }
// if (this.getDependencies().length == 0 || MinecraftServer.getExtensionManager() == null) { // it also may invoked in early class loader
// // orphaned extension, we can insert it directly
// root.addChild(loader);
// } else {
// // add children to the dependencies
// for (String dependency : this.getDependencies()) {
// if (MinecraftServer.getExtensionManager().hasExtension(dependency.toLowerCase())) {
// MinestomExtensionClassLoader parentLoader = MinecraftServer.getExtensionManager().getExtension(dependency.toLowerCase()).getOrigin().getMinestomExtensionClassLoader();
//
// // TODO should never happen but replace with better throws error.
// assert parentLoader != null;
//
// parentLoader.addChild(loader);
// }
// }
// }
return loader; return loader;
} }

View File

@ -30,8 +30,6 @@ import java.util.zip.ZipFile;
public class ExtensionManager { public class ExtensionManager {
public final static String DISABLE_EARLY_LOAD_SYSTEM_KEY = "minestom.extension.disable_early_load";
public final static Logger LOGGER = LoggerFactory.getLogger(ExtensionManager.class); public final static Logger LOGGER = LoggerFactory.getLogger(ExtensionManager.class);
public final static String INDEV_CLASSES_FOLDER = "minestom.extension.indevfolder.classes"; public final static String INDEV_CLASSES_FOLDER = "minestom.extension.indevfolder.classes";
@ -164,7 +162,8 @@ public class ExtensionManager {
// set class loaders for all extensions. // set class loaders for all extensions.
for (DiscoveredExtension discoveredExtension : discoveredExtensions) { for (DiscoveredExtension discoveredExtension : discoveredExtensions) {
try { try {
discoveredExtension.setMinestomExtensionClassLoader(discoveredExtension.makeClassLoader()); discoveredExtension.setMinestomExtensionClassLoader(discoveredExtension.makeClassLoader(discoveredExtensions)); //TODO: This is a hack to pass the dependent DiscoveredExtensions to the classloader
System.out.println("SET " + discoveredExtension.getName() + " TO " + discoveredExtension.getMinestomExtensionClassLoader());
} catch (Exception e) { } catch (Exception e) {
discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.FAILED_TO_SETUP_CLASSLOADER; discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.FAILED_TO_SETUP_CLASSLOADER;
MinecraftServer.getExceptionManager().handleException(e); MinecraftServer.getExceptionManager().handleException(e);
@ -547,7 +546,7 @@ public class ExtensionManager {
} }
getter.addMavenResolver(repoList); getter.addMavenResolver(repoList);
getter.addResolver(extensionDependencyResolver); // getter.addResolver(extensionDependencyResolver);
for (String artifact : externalDependencies.artifacts) { for (String artifact : externalDependencies.artifacts) {
var resolved = getter.get(artifact, dependenciesFolder); var resolved = getter.get(artifact, dependenciesFolder);
@ -555,11 +554,11 @@ public class ExtensionManager {
LOGGER.trace("Dependency of extension {}: {}", discoveredExtension.getName(), resolved); LOGGER.trace("Dependency of extension {}: {}", discoveredExtension.getName(), resolved);
} }
for (String dependencyName : discoveredExtension.getDependencies()) { // for (String dependencyName : discoveredExtension.getDependencies()) {
var resolved = getter.get(dependencyName, dependenciesFolder); // var resolved = getter.get(dependencyName, dependenciesFolder);
addDependencyFile(resolved, discoveredExtension); // addDependencyFile(resolved, discoveredExtension);
LOGGER.trace("Dependency of extension {}: {}", discoveredExtension.getName(), resolved); // LOGGER.trace("Dependency of extension {}: {}", discoveredExtension.getName(), resolved);
} // }
} catch (Exception e) { } catch (Exception e) {
discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.MISSING_DEPENDENCIES; discoveredExtension.loadStatus = DiscoveredExtension.LoadStatus.MISSING_DEPENDENCIES;
LOGGER.error("Failed to load dependencies for extension {}", discoveredExtension.getName()); LOGGER.error("Failed to load dependencies for extension {}", discoveredExtension.getName());
@ -570,6 +569,7 @@ public class ExtensionManager {
} }
private void addDependencyFile(@NotNull ResolvedDependency dependency, @NotNull DiscoveredExtension extension) { private void addDependencyFile(@NotNull ResolvedDependency dependency, @NotNull DiscoveredExtension extension) {
System.out.println("ADDING DEPENDENCY " + dependency.getName() + " TO " + extension.getName());
URL location = dependency.getContentsLocation(); URL location = dependency.getContentsLocation();
extension.files.add(location); extension.files.add(location);
LOGGER.trace("Added dependency {} to extension {} classpath", location.toExternalForm(), extension.getName()); LOGGER.trace("Added dependency {} to extension {} classpath", location.toExternalForm(), extension.getName());
@ -721,7 +721,7 @@ public class ExtensionManager {
// setup new classloaders for the extensions to reload // setup new classloaders for the extensions to reload
for (DiscoveredExtension toReload : extensionsToLoad) { for (DiscoveredExtension toReload : extensionsToLoad) {
LOGGER.debug("Setting up classloader for extension {}", toReload.getName()); LOGGER.debug("Setting up classloader for extension {}", toReload.getName());
toReload.setMinestomExtensionClassLoader(toReload.makeClassLoader()); // toReload.setMinestomExtensionClassLoader(toReload.makeClassLoader()); //TODO: Fix this
} }
List<Extension> newExtensions = new LinkedList<>(); List<Extension> newExtensions = new LinkedList<>();

View File

@ -11,7 +11,7 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
/** /**
* Root ClassLoader, everything goes through it before any attempt at loading is done inside this classloader * Root ClassLoader, everything goes through it before any attempt at loading is done inside this classloader
*/ */
private final MinestomRootClassLoader root; private final ClassLoader root;
/** /**
* Main of the main class of the extension linked to this classloader * Main of the main class of the extension linked to this classloader
@ -20,7 +20,7 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
private final Logger logger = LoggerFactory.getLogger(MinestomExtensionClassLoader.class); private final Logger logger = LoggerFactory.getLogger(MinestomExtensionClassLoader.class);
public MinestomExtensionClassLoader(String extensionName, String mainClassName, URL[] urls, MinestomRootClassLoader root) { public MinestomExtensionClassLoader(String extensionName, String mainClassName, URL[] urls, ClassLoader root) {
super(extensionName, urls, root); super(extensionName, urls, root);
this.root = root; this.root = root;
this.mainClassName = mainClassName; this.mainClassName = mainClassName;
@ -47,12 +47,38 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
@Override @Override
public Class<?> loadClass(String name) throws ClassNotFoundException { public Class<?> loadClass(String name) throws ClassNotFoundException {
return root.loadClass(name); try {
System.out.println("TRYING TO LOAD " + name + " IN " + getName());
return super.loadClass(name);
} catch (ClassNotFoundException e) {
System.out.println("COULD NOT LOAD, TRYING CHILDREN");
for (MinestomExtensionClassLoader child : children) {
try {
return child.loadClass(name);
} catch (ClassNotFoundException ignored) {
System.out.println("NOT FOUND IN " + child.getName() + " EITHER");
}
}
throw e;
}
} }
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return root.loadClass(name, resolve); try {
System.out.println("TRYING 2 LOAD " + name + " IN " + getName());
return super.loadClass(name, resolve);
} catch (ClassNotFoundException e) {
System.out.println("COULD NOT LOAD 2, TRYING CHILDREN");
for (MinestomExtensionClassLoader child : children) {
try {
return child.loadClass(name, resolve);
} catch (ClassNotFoundException ignored) {
System.out.println("NOT FOUND IN " + child.getName() + " EITHER");
}
}
throw e;
}
} }
/** /**
@ -62,44 +88,42 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
* @throws ClassNotFoundException if the class is not found inside this classloader * @throws ClassNotFoundException if the class is not found inside this classloader
*/ */
public Class<?> loadClassAsChild(String name, boolean resolve) throws ClassNotFoundException { public Class<?> loadClassAsChild(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name); throw new RuntimeException("Cannot load " + name + " using old mechanism");
if(loadedClass != null) { // logger.info("Loading class " + name + " as child of " + getName());
return loadedClass; // Class<?> loadedClass = findLoadedClass(name);
} // if(loadedClass != null) {
// logger.info("Found loaded class");
try { // return loadedClass;
// not in children, attempt load in this classloader // }
String path = name.replace(".", "/") + ".class"; //
InputStream in = getResourceAsStream(path); // try {
if (in == null) { // // not in children, attempt load in this classloader
throw new ClassNotFoundException("Could not load class " + name); // String path = name.replace(".", "/") + ".class";
} // InputStream in = getResourceAsStream(path);
try (in) { // if (in == null) {
byte[] bytes = in.readAllBytes(); // throw new ClassNotFoundException("Could not load class " + name);
Class<?> clazz = defineClass(name, bytes, 0, bytes.length); // }
if (resolve) { // logger.info("Found in resources");
resolveClass(clazz); // try (in) {
} // byte[] bytes = in.readAllBytes();
return clazz; // Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) { // if (resolve) {
throw new ClassNotFoundException("Could not load class " + name, e); // resolveClass(clazz);
} // }
} catch (ClassNotFoundException e) { // return clazz;
for(MinestomExtensionClassLoader child : children) { // } catch (IOException e) {
try { // throw new ClassNotFoundException("Could not load class " + name, e);
return child.loadClassAsChild(name, resolve); // }
} catch (ClassNotFoundException e1) { // } catch (ClassNotFoundException e) {
// move on to next child // for(MinestomExtensionClassLoader child : children) {
} // try {
} // return child.loadClassAsChild(name, resolve);
throw e; // } catch (ClassNotFoundException e1) {
} // // move on to next child
} // }
// }
@Override // throw e;
protected void finalize() throws Throwable { // }
super.finalize();
logger.info("Class loader " + getName() + " finalized.");
} }
/** /**

View File

@ -54,6 +54,7 @@ public class MinestomRootClassLoader extends HierarchyClassLoader {
add("kotlin"); add("kotlin");
} }
}; };
/** /**
* Used to let ASM find out common super types, without actually commiting to loading them * Used to let ASM find out common super types, without actually commiting to loading them
* Otherwise ASM would accidentally load classes we might want to modify * Otherwise ASM would accidentally load classes we might want to modify
@ -122,18 +123,19 @@ public class MinestomRootClassLoader extends HierarchyClassLoader {
try { try {
// we do not load system classes by ourselves // we do not load system classes by ourselves
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name); Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
LOGGER.trace("System class: {}", systemClass); LOGGER.info("System class: {}", systemClass);
return systemClass; return systemClass;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
try { try {
if (isProtected(name)) { if (isProtected(name)) {
LOGGER.trace("Protected: {}", name); LOGGER.info("Protected: {}", name);
return super.loadClass(name, resolve); return super.loadClass(name, resolve);
} }
LOGGER.info("Define: {}", name);
return define(name, resolve); return define(name, resolve);
} catch (Exception ex) { } catch (Exception ex) {
LOGGER.trace("Fail to load class, resorting to parent loader: " + name, ex); LOGGER.info("Fail to load class, resorting to parent loader: " + name, ex);
// fail to load class, let parent load // fail to load class, let parent load
// this forbids code modification, but at least it will load // this forbids code modification, but at least it will load
return super.loadClass(name, resolve); return super.loadClass(name, resolve);