samedi 5 mars 2016

Hot loading .jar files: random FileNotFoundException

I have a PluginManager class that watches a ./plugins directory for file creation using a WatchServiceand then utilizes a static method loadPlugin(File) from a PluginLoader class to load newly added jars at runtime. All jars in the folder are also loaded and started at application start up, in that case everything goes well, even with a bunch of plugins.
But when I drop the plugins into the folder one by one I get a weird behavior:

  • the first jar is loaded fine in 98% of the time
  • the second only in about 5%
  • the third only in a very rare case, but it happens

What I get is this:

java.io.FileNotFoundException: .\plugins\test2.jar (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird)
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(ZipFile.java:220)
at java.util.zip.ZipFile.<init>(ZipFile.java:150)
at java.util.jar.JarFile.<init>(JarFile.java:166)
at java.util.jar.JarFile.<init>(JarFile.java:130)
at PluginLoader.loadPlugin(PluginLoader.java:34)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "FileWatcher" java.lang.NullPointerException
at PluginLoader.loadPlugin(PluginLoader.java:41)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)

It is saying that the file is used in another process.

PluginLoader:

public static BasePlugin loadPlugin(File pluginJar) {
    Attributes attrib = null;
    JarFile file = null;
    try {
        file = new JarFile(pluginJar);
        Manifest manifest = file.getManifest();
        attrib = manifest.getMainAttributes();
    } catch (IOException e) {
        e.printStackTrace();
    }

    String main = attrib.getValue(Attributes.Name.MAIN_CLASS);
    String name = attrib.getValue("Plugin-Name");

    if (main == null || name == null) {
        System.out.println("Not a valid manifest: " + pluginJar.getName());
        return null;
    }

    URLClassLoader loader = null;
    try {
        loader = URLClassLoader.newInstance(new URL[] { pluginJar.toURI().toURL() }, PluginLoader.class.getClassLoader());
    } catch (MalformedURLException e2) {
        e2.printStackTrace();
    }
    Class<?> cl = null;
    try {
        cl = Class.forName(main, true, loader);
    } catch (ClassNotFoundException e1) {
        e1.printStackTrace();
    } finally {
        try {
            loader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    try {
        Class<? extends BasePlugin> c = cl.asSubclass(BasePlugin.class);
        Constructor<? extends BasePlugin> ctr = c.getConstructor(String.class, SystemManager.class);
        return ctr.newInstance(name, this.sm);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

WatchService inside PluginManager:

    private static class WatchQueueReader implements Runnable {

    private WatchService watcher;
    private PluginManager pm;

    public WatchQueueReader(PluginManager pm, WatchService watcher) {
        this.pm = pm;
        this.watcher = watcher;
    }

    @Override
    public void run() {
        try {
            WatchKey key = watcher.take();
            while (key != null) {
                for (WatchEvent<?> event : key.pollEvents()) {
                    switch (event.kind().toString()) {
                    case "ENTRY_CREATE":
                        Path dir = (Path) key.watchable();
                        Path fullPath = dir.resolve(event.context().toString());
                        BasePlugin plugin = PluginLoader.loadPlugin(fullPath.toFile());
                        if (plugin != null) {
                            this.pm.startPlugin(plugin);
                        }
                        break;
                    case "ENTRY_MODIFY":
                        break;
                    case "ENTRY_DELETE":
                        this.pm.stopPlugin(event.context().toString()); // TODO wrong name (.jar)
                        break;
                    default:
                        break;
                    }
                }
                key.reset();
                key = watcher.take();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

BasePlugin is an abstract class that plugins extend.





Aucun commentaire:

Enregistrer un commentaire