Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error updating plugin because file already in use #39

Open
hazemkmammu opened this issue May 23, 2019 · 8 comments
Open

Error updating plugin because file already in use #39

hazemkmammu opened this issue May 23, 2019 · 8 comments

Comments

@hazemkmammu
Copy link
Contributor

I tried running the example in the 'How to use' section of README.md. The update manager failed to update the plugin to the latest version because it could not delete the old version. The old version was already loaded and started before the update call.

So I unloaded the plugin being updated before calling update. This threw an exception "Plugin {} cannot be updated since it is not installed" because the plugin was unloaded. The method org.pf4j.update.UpdateManager.updatePlugin(String, String) is checking pluginManager.getPlugin(id) == null, shouldn't it instead check if the Jar/Zip exists in the pluginRoot directory. It is installed, it's just not loaded.

Right now it seems impossible to update a plugin using the default update manager.

@decebals
Copy link
Member

Thanks for reporting the issue!
Can you add more details (pf4j version, pf4j-update version, operating system. ...)? If it's not difficult for you, a tiny project that replicates the problem is very welcome and it will speed up the solution/fix.

@hazemkmammu
Copy link
Contributor Author

hazemkmammu commented May 23, 2019

org.pf4j:pf4j:2.6.0
org.pf4j:pf4j-update:2.0.0
Windows 10

Path pluginCacheDir = Paths.get( "D:\\plugincache");
PluginManager pluginManager = new DefaultPluginManager( pluginCacheDir);
pluginManager.loadPlugins();
pluginManager.startPlugins();

UpdateManager updateManager = new UpdateManager( pluginManager,
        Paths.get( "D:/plugin_repositories/repositories.json"));
// >> keep system up-to-date <<
boolean systemUpToDate = true;

// check for updates
if (updateManager.hasUpdates())
{
    List<PluginInfo> updates = updateManager.getUpdates();
    LOGGER.debug( "Found {} updates", updates.size());
    for (PluginInfo plugin : updates)
    {
        LOGGER.debug( "Found update for plugin '{}'", plugin.id);
        PluginInfo.PluginRelease lastRelease = plugin
                .getLastRelease( pluginManager.getSystemVersion(), pluginManager.getVersionManager());
        String lastVersion = lastRelease.version;
        String installedVersion = pluginManager.getPlugin( plugin.id).getDescriptor().getVersion();
//                pluginManager.stopPlugin( plugin.id);
        pluginManager.unloadPlugin( plugin.id);
        LOGGER.debug( "Update plugin '{}' from version {} to version {}", plugin.id, installedVersion,
                lastVersion);
        boolean updated = updateManager.updatePlugin( plugin.id, lastVersion);
        if (updated)
        {
            LOGGER.debug( "Updated plugin '{}'", plugin.id);
        }
        else
        {
            LOGGER.error( "Cannot update plugin '{}'", plugin.id);
            systemUpToDate = false;
        }
    }
}
else
{
    LOGGER.debug( "No updates found");
}

@hazemkmammu
Copy link
Contributor Author

Also, please note the PluginInfo.PluginRelease lastRelease = plugin .getLastRelease( pluginManager.getSystemVersion(), pluginManager.getVersionManager());. The version 2.0.0 does not have a updateManager.getLastPluginRelease(plugin.id) method like in the example in README.md.

@decebals
Copy link
Member

decebals commented May 24, 2019

@hazemkmammu
Can you create a PR (pull request) please? In a PR are very clear (visible) the modifications.

@hazemkmammu
Copy link
Contributor Author

hazemkmammu commented May 27, 2019

My earlier proposal (deleted comment) to fix the issue was incorrect. The actual issue is org.pf4j.DefaultPluginRepository.deletePluginPath(Path) throwing "java.nio.file.FileSystemException: XXX.jar: The process cannot access the file because it is being used by another process.". I am not sure why. I will update if I can figure out. Closing this issue. Sorry for the confusion.

@hazemkmammu
Copy link
Contributor Author

I believe I have isolated the cause of the issue. It seems to be somehow related to org.pf4j.LegacyExtensionFinder.readPluginsStorages(). Please see the following code snippets.

Case# 1
Throws java.nio.file.FileSystemException: D:\plugincache\SpanishGreetingPlugin.jar: The process cannot access the file because it is being used by another process..

try {
            URL jarUrl = new File("D:\\plugincache\\SpanishGreetingPlugin.jar").getCanonicalFile().toURI().toURL();
            URL[] urls = new URL[] { jarUrl };
            URLClassLoader ucl = new URLClassLoader(urls);
            Enumeration<URL> extensionResources = ucl.findResources("META-INF/extensions.idx");
            while (extensionResources.hasMoreElements()) {
                URL extensionResource = extensionResources.nextElement();
                try (Reader reader = new InputStreamReader(extensionResource.openStream(), StandardCharsets.UTF_8)) {
                }
            }
            Class<?> pluginClass = ucl.loadClass("com.fisc.pf4jdemo.SpanishGreetingPlugin$SpanishGreeting");
            GreetingExtensionPoint plugin = (GreetingExtensionPoint) pluginClass.newInstance();
            System.out.println("Greeting:" + plugin.greeting());
            ucl.close();
            FileUtils.delete(Paths.get("D:\\plugincache\\SpanishGreetingPlugin.jar"));
        } catch (ClassNotFoundException | IOException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

Case# 2
Works fine. No errors.

        try {
            URL jarUrl = new File("D:\\plugincache\\SpanishGreetingPlugin.jar").getCanonicalFile().toURI().toURL();
            URL[] urls = new URL[] { jarUrl };
            URLClassLoader ucl = new URLClassLoader(urls);
            Enumeration<URL> extensionResources = ucl.findResources("META-INF/extensions.idx");
            while (extensionResources.hasMoreElements()) {
                URL extensionResource = extensionResources.nextElement();
//                try (Reader reader = new InputStreamReader(extensionResource.openStream(), StandardCharsets.UTF_8)) {
//                }
            }
            Class<?> pluginClass = ucl.loadClass("com.fisc.pf4jdemo.SpanishGreetingPlugin$SpanishGreeting");
            GreetingExtensionPoint plugin = (GreetingExtensionPoint) pluginClass.newInstance();
            System.out.println("Greeting:" + plugin.greeting());
            ucl.close();
            FileUtils.delete(Paths.get("D:\\plugincache\\SpanishGreetingPlugin.jar"));
        } catch (ClassNotFoundException | IOException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

@hazemkmammu hazemkmammu reopened this May 28, 2019
@decebals
Copy link
Member

@hazemkmammu
Maybe is something related with pf4j/pf4j#217? The error message looks the same.

I believe I have isolated the cause of the issue.

You help a lot with your analyse. I really like that you try to find where is the problem in code and how can it be solved.

@hazemkmammu
Copy link
Contributor Author

hazemkmammu commented May 28, 2019

@decebals Thank you. You created PF4J in a simple and clean way such that it encourages contribution. I am glad I have been of help. Yes, #217 appears to be related.

I observed that using java.net.URLClassLoader.getResourceAsStream(String) instead of java.net.URLClassLoader.findResources(String) fixes this in my test code.

Case# 3
Works without errors.

try {
    URL jarUrl = new File("D:\\plugincache\\SpanishGreetingPlugin.jar").getCanonicalFile().toURI().toURL();
    URL[] urls = new URL[] { jarUrl };
    URLClassLoader ucl = new URLClassLoader(urls);
    
    Set<String> entries = new HashSet<>();
    try (Reader reader = new InputStreamReader(ucl.getResourceAsStream("META-INF/extensions.idx"))) {
        LegacyExtensionStorage.read(reader, entries);
    }
    Class<?> pluginClass = ucl.loadClass("com.fisc.pf4jdemo.SpanishGreetingPlugin$SpanishGreeting");
    GreetingExtensionPoint plugin = (GreetingExtensionPoint) pluginClass.newInstance();
    System.out.println("Greeting:" + plugin.greeting());
    ucl.close();
    FileUtils.delete(Paths.get("D:\\plugincache\\SpanishGreetingPlugin.jar"));
} catch (ClassNotFoundException | IOException | InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
}

The java.net.URLClassLoader.getResourceAsStream(String) collects the input streams returned (for files and jar files) internally and closes them when java.net.URLClassLoader.close() is called. I am not sure how that makes a difference because Case# 1 also closes the stream.

I will create a PR so that you can understand this better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants