Skip to content

Commit

Permalink
Revert "perf(plugins): add a fast path to load plugins without scanni…
Browse files Browse the repository at this point in the history
…ng (#1374)"

This reverts commit 3ed3bd8.
  • Loading branch information
junfuchen99 committed Dec 20, 2022
1 parent 62daa3c commit c7030a0
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 80 deletions.
152 changes: 74 additions & 78 deletions src/main/java/com/aws/greengrass/dependency/EZPlugins.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.aws.greengrass.logging.api.Logger;
import com.aws.greengrass.logging.impl.LogManager;
import com.aws.greengrass.util.Utils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.ClassAnnotationMatchProcessor;
Expand All @@ -15,23 +16,20 @@

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import javax.inject.Inject;

Expand All @@ -41,7 +39,6 @@ public class EZPlugins implements Closeable {
private static final Logger logger = LogManager.getLogger(EZPlugins.class);
public static final String JAR_FILE_EXTENSION = ".jar";
private final List<Consumer<FastClasspathScanner>> matchers = new ArrayList<>();
private final List<Consumer<Class<?>>> classMatchers = new ArrayList<>();
private Path cacheDirectory;
@Getter
private Path trustedCacheDirectory;
Expand Down Expand Up @@ -87,29 +84,14 @@ public final EZPlugins withCacheDirectory(Path d) throws IOException {

private synchronized void loadPlugins(boolean trusted, ClassLoader cls) {
doneFirstLoad = true;
if (trusted) {
root = cls;
}

// Try and find the Greengrass plugin class (fast path)
try {
if (cls instanceof URLClassLoader) {
Class<?> clazz = findGreengrassPlugin((URLClassLoader) cls);
if (clazz != null) {
classMatchers.forEach(m -> m.accept(clazz));
return;
}
}
} catch (IOException e) {
logger.atWarn().log("Problem looking for Greengrass plugin with the fast path."
+ " Falling back to classpath scanner", e);
}

FastClasspathScanner sc = new FastClasspathScanner("com.aws.greengrass");
sc.strictWhitelist();
sc.addClassLoader(cls);
matchers.forEach(m -> m.accept(sc));
sc.scan(executorService, 1);
if (trusted) {
root = cls;
}
}

@SuppressWarnings("PMD.CloseResource")
Expand All @@ -124,69 +106,26 @@ private void loadPlugins(boolean trusted, Path p) throws IOException {
* Load a single plugin with the classpath scanner.
*
* @param p path to jar file
* @param annotationClass annotation to search for
* @param <T> annotation class type
* @param matcher matcher to use
* @throws IOException if loading the class fails
*/
// Class loader must stay open, otherwise we won't be able to load all classes from the jar
@SuppressWarnings("PMD.CloseResource")
public synchronized <T extends Annotation> ClassLoader loadPluginAnnotatedWith(Path p, Class<T> annotationClass,
Consumer<Class<?>> matcher) throws IOException {
public synchronized ClassLoader loadPlugin(Path p, Consumer<FastClasspathScanner> matcher) throws IOException {
URL[] urls = {p.toUri().toURL()};
return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> {
URLClassLoader cl = new URLClassLoader(urls, root);
classLoaders.add(cl);
root = cl;

// Try and find the Greengrass plugin class (fast path)
try {
Class<?> clazz = findGreengrassPlugin(cl);
if (clazz != null) {
if (clazz.isAnnotationPresent(annotationClass)) {
matcher.accept(clazz);
return cl;
} else {
logger.atWarn().log("Class {} was found, but not annotated with {}",
clazz.getSimpleName(), annotationClass.getSimpleName());
}
}
} catch (IOException e) {
logger.atWarn().log("IOException reading from {}. Falling back to classpath scanner", p, e);
}

FastClasspathScanner sc = new FastClasspathScanner();
sc.ignoreParentClassLoaders();
sc.addClassLoader(cl);
sc.matchClassesWithAnnotation(annotationClass, matcher::accept);
matcher.accept(sc);
sc.scan(executorService, 1);
return cl;
});
}

private synchronized Class<?> findGreengrassPlugin(URLClassLoader cls) throws IOException {
URL url = cls.findResource("META-INF/MANIFEST.MF");
if (url == null) {
return null;
}
URLConnection conn = url.openConnection();
// Workaround JDK bug: https://bugs.openjdk.org/browse/JDK-8246714
conn.setUseCaches(false);
try (InputStream is = conn.getInputStream()) {
Manifest manifest = new Manifest(is);
Attributes attr = manifest.getMainAttributes();
if (attr != null) {
String className = attr.getValue("GG-Plugin-Class");
if (className != null) {
return cls.loadClass(className);
}
}
} catch (ClassNotFoundException e) {
logger.atWarn().log("Class specified by the GG-Plugin-Class manifest entry was not found", e);
}
return null;
}

// Only use in tests to scan our own classpath for @ImplementsService
public synchronized EZPlugins scanSelfClasspath() {
loadPlugins(true, this.getClass().getClassLoader());
Expand Down Expand Up @@ -257,6 +196,73 @@ public String[] list(boolean trusted) {
return s.toArray(new String[0]);
}

/**
* Delete all plugins.
*
* @return this
* @throws IOException if deletion fails
*/
public EZPlugins clearCache() throws IOException {
IOException ioe = new IOException("One or more file deletion failed");
walk(cacheDirectory, p -> {
if (p.toString().endsWith(JAR_FILE_EXTENSION)) {
try {
Files.delete(p);
} catch (IOException e) {
ioe.addSuppressed(e);
}
}
});
if (ioe.getSuppressed().length > 0) {
throw ioe;
}
return this;
}

/**
* Load a jar from a URL into the plugin cache.
*
* @param trusted true if the plugin should be set as trusted
* @param u URL to load the jar from
* @return this
* @throws IOException if loading fails
*/
public EZPlugins loadToCache(boolean trusted, URL u) throws IOException {
String nm = Utils.namePart(u.getPath());
if (!nm.endsWith(JAR_FILE_EXTENSION)) {
throw new IOException("Only .jar files can be cached: " + u);
}
Path d = (trusted ? trustedCacheDirectory : untrustedCacheDirectory).resolve(nm);
Files.copy(u.openStream(), d, StandardCopyOption.REPLACE_EXISTING);
loadPlugins(trusted, d);
return this;
}

/**
* Move a jar from the path into the plugin cache.
*
* @param trusted true if it should be moved into the trusted plugin cache
* @param u path to the jar to move
* @return this
* @throws IOException if moving fails
*/
public EZPlugins moveToCache(boolean trusted, Path u) throws IOException {
Path p = u.getFileName();
if (p == null) {
throw new IOException("Filename was null");
}
String nm = p.toString();
if (!nm.endsWith(JAR_FILE_EXTENSION)) {
throw new IOException("Only .jar files can be cached: " + u);
}
Path d = (trusted ? trustedCacheDirectory : untrustedCacheDirectory).resolve(nm);
if (!d.equals(u)) {
Files.copy(u, d, StandardCopyOption.REPLACE_EXISTING);
}
loadPlugins(trusted, d);
return this;
}

/**
* Find plugins implementing the given class.
*
Expand All @@ -271,11 +277,6 @@ public <T> EZPlugins implementing(Class<T> c, ImplementingClassMatchProcessor<T>
throw new IllegalStateException("EZPlugins: all matchers must be specified before the first class load");
}
matchers.add(fcs -> fcs.matchClassesImplementing(c, m));
classMatchers.add(x -> {
if (x.isAssignableFrom(c)) {
m.processMatch((Class<? extends T>) x);
}
});
return this;
}

Expand All @@ -293,11 +294,6 @@ public <T extends Annotation> EZPlugins annotated(Class<T> c, ClassAnnotationMat
throw new IllegalStateException("EZPlugins: all matchers must be specified before the first class load");
}
matchers.add(fcs -> fcs.matchClassesWithAnnotation(c, m));
classMatchers.add((x) -> {
if (x.isAnnotationPresent(c)) {
m.processMatch(x);
}
});
return this;
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/aws/greengrass/lifecyclemanager/Kernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ private Class<?> locateExternalPlugin(String name, Topics serviceRootTopics) thr
try {
AtomicReference<Class<?>> classReference = new AtomicReference<>();
EZPlugins ezPlugins = context.get(EZPlugins.class);
ezPlugins.loadPluginAnnotatedWith(pluginJar, ImplementsService.class, (c) -> {
ezPlugins.loadPlugin(pluginJar, (sc) -> sc.matchClassesWithAnnotation(ImplementsService.class, (c) -> {
// Only use the class whose name matches what we want
ImplementsService serviceImplementation = c.getAnnotation(ImplementsService.class);
if (serviceImplementation.name().equals(name)) {
Expand All @@ -562,7 +562,7 @@ private Class<?> locateExternalPlugin(String name, Topics serviceRootTopics) thr
}
classReference.set(c);
}
});
}));
clazz = classReference.get();
} catch (Throwable e) {
throw new ServiceLoadException(String.format("Unable to load %s as a plugin", name), e);
Expand Down

0 comments on commit c7030a0

Please sign in to comment.