Skip to content

Fix/plugin loading and cycles#1036

Open
TheWakz wants to merge 9 commits into
Col-E:masterfrom
TheWakz:fix/plugin-loading-and-cycles
Open

Fix/plugin loading and cycles#1036
TheWakz wants to merge 9 commits into
Col-E:masterfrom
TheWakz:fix/plugin-loading-and-cycles

Conversation

@TheWakz

@TheWakz TheWakz commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What's new

  • Improved Plugin Loading Performance: Registered PluginClassLoaderImpl as parallel-capable to prevent potential thread deadlocks during concurrent plugin loading.
  • Staged Loading Visibility: Resolved dependency classloaders before enabling plugins. This ensures correct class visibility for plugins loaded in the same batch, where dependencies exist in the staged load set but are not yet committed to the main plugin map.

What's fixed & Refactored

1. Lifecycle & Topology Robustness

  • Cyclic Dependency Protection: Introduced cycle detection during recursive enablement and dependent collection. Circular dependencies within the plugin graph are now explicitly detected and rejected with a PluginException, preventing infinite loops or StackOverflowError.
  • Idempotent Plugin Unload: Tracked already unloaded plugins during recursive unloads. This prevents the same dependent plugin from being unloaded multiple times when reachable through multiple paths (e.g., diamond dependency graphs).
  • Graceful Fault Tolerance (Rollback): The plugin instance is now stored before invoking onEnable. If enablement fails after partial initialization, the system reliably calls onDisable to clean up the plugin state.

2. Class Loading & Resource Isolation

  • Standard Class Loading Discipline: Switched to dependencyLoader.loadClass(name) instead of calling findClass directly. This preserves parent delegation, class loading locks, and already-loaded class checks.
  • Fine-Grained Synchronization: Synchronized local class lookup on getClassLoadingLock(name) to align with the parallel-capable classloader discipline and avoid racing defineClass.
  • Resource URI & Stream Corrections: * Included the plugin ID in generated resource URLs to avoid ambiguity when multiple plugins share identical resource paths.
    • Corrected the URI structure by passing the resource name as the path component instead of the URI fragment, fixing compatibility with standard methods like URL.getPath().
    • Replaced InputStream caching in plugin URL connections with fresh streams from the backing ByteSource to prevent closed/partially-read stream bugs.

TheWakz added 3 commits June 25, 2026 11:51
Stop caching InputStream instances in plugin URL connections.

Each getInputStream call now opens a fresh stream from the backing ByteSource, avoiding partially-read or already-closed streams on repeated access.
Implement findResources so plugin resources are visible to APIs that enumerate resources.

This improves compatibility with ServiceLoader, SPI discovery, and code that searches META-INF/services or other resources through ClassLoader#getResources.
Include the plugin id in generated plugin resource URLs.

This avoids ambiguous resource URLs when multiple plugins contain resources with the same path but different contents.
@Col-E Col-E added the bug Yup, thats broken label Jun 25, 2026
TheWakz added 6 commits June 26, 2026 15:07
Resolve plugin dependencies before enablement and pass the resolved dependency classloaders directly into each plugin classloader.

This fixes class visibility for plugins loaded in the same batch, where dependencies exist in the staged load set but are not yet committed to the main plugin map.
…loading

Register the plugin classloader as parallel-capable and synchronize local class lookup on getClassLoadingLock(name).

Since the classloader is now marked as parallel-capable, both findClass and direct lookupClass calls must use the same fine-grained class loading lock discipline to avoid racing defineClass for the same type under concurrent loading.
Use dependencyLoader.loadClass(name) instead of calling findClass directly.

This preserves parent delegation, class loading locks, and already-loaded class checks when resolving classes from plugin dependencies.
Store the plugin instance before invoking onEnable so rollback can call onDisable when enablement fails after partial initialization.

This makes failed plugin loads clean up plugin state more reliably.
Track unloaded plugins during recursive unload.

This prevents the same dependant plugin from being unloaded multiple times when it is reachable through multiple dependency paths, such as diamond dependency graphs.
…rsal

Track currently visiting plugins during recursive enablement and dependant collection.

This ensures that circular dependencies within the plugin graph are explicitly detected and rejected with an exception, preventing infinite loops or stack overflows during topology traversal.
@TheWakz TheWakz force-pushed the fix/plugin-loading-and-cycles branch from 97a193d to 6c80b7a Compare June 29, 2026 08:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Yup, thats broken

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants