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

Avoid instantiating Plugin class if a plugin is already unloaded #570

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

slovdahl
Copy link
Contributor

@slovdahl slovdahl commented Mar 11, 2024

Given the following preconditions:

  • PluginClassLoader is configured to use ClassLoadingStrategy PDA, DPA or DAP.
  • There is a PluginStateListener that calls event.getPlugin().getPlugin().
  • A plugin (plugin-a) with a missing dependency is loaded (plugin-a).
  • The Plugin class in plugin-a extends, implements or references a class in either plugin-b or in the main application.

In that scenario, when plugin-a is unloaded by pf4j, and the PluginStateListener is run, the event.getPlugin().getPlugin() call will fail with a NullPointerException. This is caused by the class loading done when instantiating the Plugin class in plugin-a. Because not all classes needed are found in plugin-a itself, pf4j will continue by looking up classes in plugin-a's dependencies. Because plugin-b is missing and has no associated ClassLoader.

@@ -90,7 +90,7 @@ public ClassLoader getPluginClassLoader() {
* @return the plugin instance
*/
public Plugin getPlugin() {
if (plugin == null) {
if (plugin == null && pluginState != PluginState.UNLOADED) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@decebals thoughts about this? Does it make any sense at all? 🤔

We have some internal tests that use custom-built plugins, and now that I was rebuilding them to use a base org.pf4j.Plugin class coming from the application itself, those tests started failing.

The NPE is coming from the PluginStateListener that tries to tear down the Spring application context for unloaded plugins. We can work around it there too, but I figured it could make sense to avoid this in pf4j itself too.

In our PluginStateListener we did something like this before:

if (event.getPluginState() == PluginState.UNLOADED) {
  if (pluginWrapper.getPlugin() instanceof SpringPlugin springPlugin) {
    ApplicationContext context = springPlugin.getPluginApplicationContext();
    if (childApplicationContext instanceof ConfigurableApplicationContext) {
      ((ConfigurableApplicationContext)context).close();
    }
  }
}

The workaround is to only do it if the previous state was PluginState.STOPPED:

if (event.getPluginState() == PluginState.UNLOADED && event.getOldState() == PluginState.STOPPED) {
  if (pluginWrapper.getPlugin() instanceof SpringPlugin springPlugin) {
    ApplicationContext context = springPlugin.getPluginApplicationContext();
    if (childApplicationContext instanceof ConfigurableApplicationContext) {
      ((ConfigurableApplicationContext)context).close();
    }
  }
}

I would like to write some tests for this in pf4j too, but it requires a bit of new infrastructure for being able to create plugins that actually contain fully working classes.

Given the following preconditions:
- `PluginClassLoader` is configured to use `ClassLoadingStrategy` `PDA`, `DPA` or `DAP`.
- There is a `PluginStateListener` that calls `event.getPlugin().getPlugin()`.
- A plugin (`plugin-a`) with a missing dependency is loaded (`plugin-a`).
- The `Plugin` class in `plugin-a` extends, implements or references a class in either
  `plugin-b` or in the main application.

In that scenario, when `plugin-a` is unloaded by pf4j, and the `PluginStateListener`
is run, the `event.getPlugin().getPlugin()` call will fail with a `NullPointerException`.
This is caused by the class loading done when instantiating the `Plugin` class in
`plugin-a`. Because not all classes needed are found in `plugin-a` itself, pf4j will
continue by looking up classes in `plugin-a`'s dependencies. Because `plugin-b` is
missing and has no associated `ClassLoader`.
@slovdahl slovdahl force-pushed the avoid-plugin-class-instantiation-for-unloaded-plugin branch from 4c24733 to 3a467de Compare March 11, 2024 09:46
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

Successfully merging this pull request may close these issues.

None yet

1 participant