-
-
Notifications
You must be signed in to change notification settings - Fork 88
feat: implement plugin architecture with isolated class loaders #3260
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
Conversation
Summary of ChangesHello @robfrank, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances ArcadeDB's extensibility by introducing a modern and robust plugin architecture. The core of this change is the implementation of isolated class loaders for each plugin, which effectively eliminates dependency conflicts and allows plugins to use different versions of libraries. This new system streamlines plugin discovery and lifecycle management through a dedicated PluginManager and PluginDescriptor, making it easier to develop, deploy, and manage server extensions. The existing built-in plugins have been migrated to this new framework, and extensive documentation has been provided to guide future plugin development. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
🧪 CI InsightsHere's what we observed from your CI run for be0929a. 🟢 All jobs passed!But CI Insights is watching 👀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a well-designed plugin architecture using isolated class loaders, which is a significant improvement for modularity and dependency management. The implementation correctly uses Java's ServiceLoader for discovery and carefully manages the plugin lifecycle, including thread context class loaders. The changes are backward compatible with the legacy plugin system. My main concern is the lack of comprehensive unit tests for the new PluginManager, which currently only cover the empty/no-plugin scenario. I've also noted a minor opportunity for code cleanup by removing an unused field.
| public class PluginManagerTest { | ||
| private ArcadeDBServer server; | ||
| private PluginManager pluginManager; | ||
|
|
||
| @BeforeEach | ||
| public void setup() { | ||
| final ContextConfiguration configuration = new ContextConfiguration(); | ||
| configuration.setValue(GlobalConfiguration.SERVER_ROOT_PATH, "./target/test-server"); | ||
| configuration.setValue(GlobalConfiguration.SERVER_DATABASE_DIRECTORY, "./target/test-server/databases"); | ||
|
|
||
| server = new ArcadeDBServer(configuration); | ||
| pluginManager = new PluginManager(server, configuration); | ||
| } | ||
|
|
||
| @AfterEach | ||
| public void teardown() { | ||
| if (server != null && server.isStarted()) { | ||
| server.stop(); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testPluginManagerCreation() { | ||
| assertNotNull(pluginManager); | ||
| assertEquals(0, pluginManager.getPluginCount()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDiscoverPluginsWithNoDirectory() { | ||
| // Should handle missing plugins directory gracefully | ||
| pluginManager.discoverPlugins(); | ||
| assertEquals(0, pluginManager.getPluginCount()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetPluginNames() { | ||
| final Collection<String> names = pluginManager.getPluginNames(); | ||
| assertNotNull(names); | ||
| assertTrue(names.isEmpty()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetPlugins() { | ||
| final Collection<ServerPlugin> plugins = pluginManager.getPlugins(); | ||
| assertNotNull(plugins); | ||
| assertTrue(plugins.isEmpty()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testStopPluginsWhenEmpty() { | ||
| // Should handle stopping with no plugins loaded | ||
| assertDoesNotThrow(() -> pluginManager.stopPlugins()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests for PluginManager are a good starting point, but they only cover the scenario where no plugins exist. The core functionality—discovering, loading, starting, and stopping a plugin—is not tested. This represents a significant testing gap for a critical new feature. It is highly recommended to add tests that cover the main success path, for instance by creating a dummy plugin JAR during the test build and verifying its entire lifecycle.
server/src/main/java/com/arcadedb/server/plugin/PluginManager.java
Outdated
Show resolved
Hide resolved
Pull Request Review: Plugin Architecture with Isolated Class LoadersThis PR introduces a sophisticated plugin system with isolated class loaders. Overall, this is a well-architected solution that addresses dependency isolation. Below is my detailed feedback: StrengthsArchitecture and Design
Implementation Quality
Issues and Concerns1. Critical: Missing Null Check in ArcadeDBServer.javaLocation: server/src/main/java/com/arcadedb/server/ArcadeDBServer.java:406
Fix: Add null check before calling discoverPlugins() 2. Security: Class Loader Leak PreventionLocation: server/src/main/java/com/arcadedb/server/plugin/PluginClassLoader.java The current implementation doesn't prevent class loader leaks when plugins hold references that prevent GC. Consider documenting that plugins MUST release all resources in stopService(). 3. Thread Safety: ConcurrentHashMap Not Fully UtilizedLocation: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:705 classLoaderMap is declared as ConcurrentHashMap but never actually used. The main plugins map is a LinkedHashMap which is NOT thread-safe. If multiple threads call plugin methods concurrently, LinkedHashMap can become corrupted. Recommendation: Either remove classLoaderMap if unused, or synchronize all access to plugins map, or use ConcurrentHashMap for plugins as well. 4. Resource Management: URLClassLoader Not Closed on Load FailureLocation: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:742-773 If ServiceLoader finds no plugins, the method returns without closing the class loader, causing a resource leak. The classLoader should be closed in the early return path. 5. Documentation: Missing transitive=false ExplanationLocation: package/src/main/assembly/full.xml:363 The useTransitiveDependencies=false setting is critical but undocumented. Without explanation, developers might not understand why plugin dependencies aren't being included, potentially causing ClassNotFoundException at runtime. Suggestions for Improvement1. Enhanced Error MessagesPluginManager.loadPlugin() should provide more diagnostic information when plugin loading fails (JAR permissions, missing META-INF/services file, invalid class names, instantiation failures). 2. Test Coverage GapsCurrent tests (PluginManagerTest.java) only test the happy path. Add tests for:
3. Plugin Dependencies ClarificationThe documentation states plugins can have different versions of dependencies, but useTransitiveDependencies=false prevents this. Consider using Maven Shade plugin to create fat JARs for plugins, or document that plugins must share all dependencies from lib/. 4. Breaking Change DocumentationMoving plugins to lib/plugins/ changes the distribution structure. This affects deployment scripts, Docker images, and custom plugin loading scripts. Document this breaking change in release notes with a migration guide. Checklist ReviewFrom PR description:
Overall AssessmentRating: 4/5 stars This is a well-designed feature that solves a real problem. The architecture is sound, documentation is comprehensive, and backward compatibility is maintained. However, there are several resource management and thread safety issues that should be addressed before merge. Must Fix Before Merge:
Should Fix Before Merge:
Nice to Have:
Recommendation: Request changes for critical fixes, then approve after addressing must-fix items. The implementation follows ArcadeDB conventions well with proper licensing headers, consistent code style, and uses existing LogManager and CodeUtils utilities. |
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
|
@claude address the comments: improve test coverage |
|
Claude finished @robfrank's task in 2m 55s —— View job Improving Plugin Test Coverage ✓Successfully addressed review feedback by significantly expanding test coverage for the plugin system. Completed Tasks
Summary of ChangesI've expanded New Test Coverage:
Test Plugin Classes:
Key Testing Patterns:
The expanded test suite now covers all the scenarios mentioned in the review feedback, including success paths, failure scenarios, class loader isolation, and lifecycle management. Note: I don't have approval to run Maven tests in this environment, but the code compiles successfully. The CI pipeline will validate all tests pass. Commit: 742587e |
|
@claude : only the "full" package is using the new folder structure. update also minimal and headless and base assembly. |
|
Claude finished @robfrank's task in 1m 31s —— View job Updating Assembly Files for Plugin Folder Structure ✓Successfully updated all assembly files to use the new plugin folder structure. Completed Tasks
Summary of ChangesAll assembly files now follow the same plugin deployment pattern as full.xml: minimal.xml and headless.xml:
base.xml:
This ensures all distribution types (full, minimal, headless, base) now deploy plugins to Commit: e190512 |
|
@claude the package/arcadedb-builder.sh needs to be updated as well |
|
Claude finished @robfrank's task in 1m 33s —— View job Updating arcadedb-builder.sh for Plugin Folder Structure ✓Successfully updated the builder script to handle the new plugin directory structure. Completed Tasks
Summary of ChangesUpdated Changes in
The builder script now correctly separates plugins into the isolated class loader directory structure, matching the changes made to the Maven assembly files (full.xml, minimal.xml, headless.xml, base.xml). Testing Notes:
Commit: 8400f61 |
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
Code Review - Plugin Architecture with Isolated Class LoadersOverviewThis PR introduces a comprehensive plugin architecture with isolated class loaders, addressing issue #157. The implementation follows the Java ServiceLoader pattern and provides proper isolation between plugins while sharing server API classes. Overall, this is a well-designed and well-documented implementation. Strengths
Critical Issues1. Assembly Configuration Bug in full.xml (lines 118-131)Location: The plugin dependencySet uses Impact: Plugin JARs won't be packaged in 2. Breaking API Change Without DeprecationLocation: The enum Impact: This breaks ALL existing third-party plugins that implement Recommendation: Deprecate the old enum first or provide a migration path. Major Issues3. Commented-Out Legacy CodeLocation: Commented code should be removed, not left in place. This creates confusion and clutter. 4. Missing GraphQL and Studio from Plugin SystemLocation: The assembly excludes 5. Resource Cleanup Risk in PluginManager.loadPlugin()Location: If an exception occurs during ServiceLoader iteration, the classLoader is never closed, causing a resource leak. Recommendation: Use try-with-resources or ensure cleanup in finally block. Minor Issues and Suggestions6. Inconsistent Plugin Name HandlingTwo different naming sources create confusion (JAR filename vs plugin class getName). Recommendation: Consistently use 7. slf4j Dependency MoveMoving 8. Missing Null Check in PluginClassLoaderNo null checks on parameters in constructor. Add validation with 9. Unused pluginName ParameterThe 10. Potential Race Condition in PluginManager
11. Missing Documentation for Plugin ConfigurationDocumentation should clarify:
Security Considerations12. Plugin Class Loading SecurityThe class loader allows plugins to load any classes from their JARs with child-first priority. While this is by design, consider:
Positive Design Decisions
Overall AssessmentThis is a high-quality implementation of a much-needed feature. The architecture is sound, documentation is excellent, and testing is comprehensive. However, there is one critical bug (assembly configuration) that must be fixed before merge, and one breaking API change that should be handled more carefully. Recommendation: Request changes for critical issues #1 and #2, then approve after fixes. Action ItemsMust Fix Before Merge:
Should Fix: Nice to Have: Great work overall! The plugin system will be a valuable addition to ArcadeDB. 🚀 |
b0e7a67 to
7012832
Compare
Pull Request Review: Plugin Architecture with Isolated Class LoadersSummaryThis PR introduces a robust plugin architecture for ArcadeDB using isolated class loaders and the Java ServiceLoader pattern. The implementation is well-structured and follows good Java practices. Overall, this is strong work with comprehensive testing and documentation. ✅ Strengths1. Excellent Architecture Design
2. Comprehensive Test CoverageThe
3. Good Documentation
4. Thread Safety
🔴 Critical Issues1. Resource Leak in Plugin Loading (server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:164)When a plugin is skipped due to configuration, the classloader is closed, but if the ServiceLoader iteration fails or throws an exception after creating the classloader but before registration, the classloader won't be closed. Problem: private void loadPlugin(final File pluginJar) throws Exception {
final PluginClassLoader classLoader = new PluginClassLoader(pluginName, pluginJar, getClass().getClassLoader());
final ServiceLoader<ServerPlugin> serviceLoader = ServiceLoader.load(ServerPlugin.class, classLoader);
for (ServerPlugin pluginInstance : serviceLoader) {
// If an exception occurs here, classLoader is never closed
if (configuredPlugins.contains(name) || ...) {
plugins.put(name, descriptor);
classLoaderMap.put(classLoader, descriptor);
} else {
classLoader.close(); // Only closed in this branch
}
}
// If no plugins found in ServiceLoader, classLoader is leaked
}Solution: Use try-with-resources or ensure cleanup in finally block: private void loadPlugin(final File pluginJar) throws Exception {
final PluginClassLoader classLoader = new PluginClassLoader(pluginName, pluginJar, getClass().getClassLoader());
boolean registered = false;
try {
final ServiceLoader<ServerPlugin> serviceLoader = ServiceLoader.load(ServerPlugin.class, classLoader);
for (ServerPlugin pluginInstance : serviceLoader) {
// ... existing logic ...
if (configuredPlugins.contains(name) || ...) {
plugins.put(name, descriptor);
classLoaderMap.put(classLoader, descriptor);
registered = true;
}
}
} finally {
if (\!registered) {
classLoader.close();
}
}
}2. Incorrect META-INF Path for Bolt PluginThe bolt plugin uses the wrong path:
This is a critical bug - the bolt plugin won't be discovered by ServiceLoader! All other plugins (gremlin, postgresw, mongodbw, redisw, grpcw) use the correct 🟡 Important Issues3. Missing Duplicate Plugin Handling (PluginManager.java:151-154)When a duplicate plugin name is detected, the code logs a warning but continues iterating. This could:
Recommendation: Add if (plugins.containsKey(name)) {
LogManager.instance().log(this, Level.WARNING, "Plugin with name '%s' is already loaded", name);
classLoader.close(); // Close the duplicate's classloader
break; // Exit the ServiceLoader iteration
}4. Inconsistent Plugin Configuration Logic (PluginManager.java:156-157)The plugin loading checks three different identifiers: if (configuredPlugins.contains(name) ||
configuredPlugins.contains(pluginName) ||
configuredPlugins.contains(pluginInstance.getClass().getName()))Where:
Issue: The
Recommendation: Document this behavior in 5. Thread Context ClassLoader Not Restored on Exception (PluginManager.java:188-206)In Minor Issue: The exception message doesn't include which priority level failed, making debugging harder. Recommendation: } catch (final Exception e) {
throw new ServerException("Error starting plugin: " + pluginName +
" (priority: " + priority + ")", e);
}🔵 Minor Issues / Suggestions6. Unused
|
Code Review: Plugin Architecture with Isolated Class LoadersOverviewThis PR introduces a comprehensive plugin architecture that uses isolated class loaders to prevent dependency conflicts. The implementation is well-structured and addresses a critical need for plugin isolation. Overall, this is a high-quality implementation with excellent test coverage and documentation. Strengths1. Excellent Architecture Design
2. Comprehensive Documentation
3. Strong Test Coverage
4. Proper Resource Management
Issues & RecommendationsCritical Issues1. Resource Leak in Duplicate Plugin Detection (PluginManager.java:152-155)Location: When a duplicate plugin is detected, the code breaks out of the loop, but the classloader is only closed if 2. Potential ClassLoader Leak in Main ClassLoader Discovery (PluginManager.java:82-96)Location: The Recommendation: Add a comment explaining that plugins loaded from the main classloader cannot be unloaded and are intended for built-in plugins only. Major Issues3. Inconsistent Plugin Name ResolutionLocation: The code uses different strategies for getting plugin names:
This inconsistency could lead to plugins being registered under different names depending on how they're loaded. Recommendation: Use 4. classLoaderMap Not UsedLocation: The Recommendation: Either use this map for plugin lookup by classloader (useful for debugging) or remove it entirely. Minor Issues5. Commented-Out Code in ArcadeDBServer.javaLocation: There are three commented-out lines for the old Recommendation: Remove the commented lines to keep the codebase clean. 6. Empty Line in ConstructorLocation: Unnecessary empty line in the constructor. 7. Missing Null Check in ServerPlugin InterfaceLocation: The default Recommendation: default String getName() {
String name = this.getClass().getSimpleName();
return name.isEmpty() ? this.getClass().getName() : name;
}8. Dependency Change in pom.xml May Have Unintended EffectsLocation: Moving slf4j-jdk14 from engine to parent pom changes its scope for all modules. Ensure this doesn't conflict with other logging implementations in plugins. Recommendation: Document why this change was necessary and verify no logging conflicts occur. Code Quality9. Magic String for Plugin PathLocation: The plugins path is constructed with string concatenation. Consider extracting to a constant. private static final String PLUGINS_SUBDIR = "lib" + File.separator + "plugins";
// then use:
this.pluginsDirectory = server.getRootPath() + File.separator + PLUGINS_SUBDIR;10. Exception Message Could Be More HelpfulLocation: The error message includes the priority but not the actual exception message from the cause. Recommendation: throw new ServerException("Error starting plugin: " + pluginName +
" (priority: " + priority + "): " + e.getMessage(), e);Performance Considerations11. ServiceLoader Iteration Creates New InstancesServiceLoader creates fresh instances each time it's iterated. This is working as intended in the current code, but be aware that ServiceLoader caching is disabled for plugin isolation. 12. LinkedHashMap for Plugin OrderingGood choice to use LinkedHashMap for preserving insertion order. This ensures predictable plugin startup/shutdown sequences. Security ConsiderationsPositive
Recommendations
Test Coverage AssessmentExcellent Coverage
Missing Test Cases
Compliance with CLAUDE.md Guidelines✅ Adheres to coding standards: Yes, consistent with existing codebase style Recommendations SummaryMust Fix Before Merge
Should Fix
Nice to Have
ConclusionThis is an excellent PR that introduces a much-needed plugin isolation architecture. The code quality is high, documentation is comprehensive, and test coverage is strong. The issues identified are mostly minor improvements and cleanup tasks. Recommendation: ✅ Approve with minor changes - Address the critical items (remove commented code, fix naming consistency, handle unused map) and this will be ready to merge. Great work on this feature! The isolated classloader approach will significantly improve plugin management and prevent dependency conflicts. Review performed using Claude Code analyzer |
Pull Request Review: Plugin Architecture with Isolated Class LoadersThis PR implements a robust plugin architecture using isolated class loaders, resolving issue #157. ✅ Strengths1. Excellent Architecture Design
2. Comprehensive Test Coverage
3. Good Documentation
4. Resource Management
🔍 Issues and Concerns1. Potential ClassLoader Memory Leak (HIGH) Issue: Closing URLClassLoader does not guarantee GC. Thread references to plugin classes prevent collection. Recommendations:
2. Configuration Parsing Inconsistency (MEDIUM) Issue: Confusing matching logic with multiple name checks. Recommendation: Clarify and document expected config format. 3. Missing PluginClassLoader Validation (MEDIUM) Issue: No protection against plugins packaging com.arcadedb classes. Recommendation: Add validation to warn about shadowing. 4. Context ClassLoader for Async (MEDIUM) Issue: Spawned threads may not inherit correct classloader. Recommendation: Document thread creation best practices. 🎯 Performance
🔒 Security
📝 Minor Issues
🧪 Testing Recommendations
🎉 SummaryWell-architected PR solving real dependency conflict problems. Severity: High (classloader leak), Medium (config/validation), Low (docs) Recommendation: Approve with Minor Revisions Before merge:
Great work improving ArcadeDB extensibility! 🚀 |
Pull Request Review: Plugin Architecture with Isolated Class LoadersThis PR introduces a significant architectural improvement by implementing a plugin system with isolated class loaders. The implementation is well-designed and follows industry best practices. ✅ Strengths1. Excellent Architecture Design
2. Strong Resource Management
3. Comprehensive Testing
4. Documentation
5. Backward Compatibility
🔍 Issues & Recommendations1. CRITICAL: Assembly Configuration Inconsistency Issue: useTransitiveDependencies=false means plugin dependencies won't be packaged. Plugins like Gremlin have many dependencies (Netty, Groovy, Commons) that won't be included. Recommendation: Add Maven Shade Plugin to plugin modules to create fat JARs with dependencies. 2. Moderate: Configuration Validation Missing The getConfiguredPlugins() method doesn't validate format. Add validation for malformed entries. 3. Minor: Logging Levels Change Level.INFO to Level.FINE for routine conditions like missing directories. 4. Minor: Thread Safety 🔒 Security ConsiderationsGood: Class loading restrictions, plugin validation, exception isolation ✅ Recommendation: Approve with ChangesExcellent implementation! Core is solid with good resource management and testing. Required Changes:
Suggested Changes:
Review Score: 8/10 |
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
51d8275 to
03da450
Compare
Code Review - Plugin Architecture with Isolated Class LoadersThis is an excellent architectural improvement that addresses issue #157 by implementing a proper plugin system with isolated class loaders. Overall, the implementation is solid and well-tested. Below are my findings: ✅ StrengthsArchitecture & Design
Code Quality
Testing
Documentation
🔍 Issues & ConcernsCritical Issues1. Inconsistent Plugin Naming (PluginManager.java:86, 146, 149)The code uses different approaches to get plugin names:
Problem: This can cause confusion and potential mismatches. The getName() method returns Recommendation: Use // Line 86-91 - should use getName() consistently
for (ServerPlugin pluginInstance : serviceLoader) {
String name = pluginInstance.getName(); // ✓ Consistent with line 149
if (configuredPlugins.contains(name) || configuredPlugins.contains(pluginInstance.getClass().getName())) {
// ...
}
}2. Potential Memory Leak in Error Scenarios (PluginManager.java:152-156)When a duplicate plugin is detected, the code breaks out of the loop, but the comment says "classloader will be closed in finally block". However, looking at the finally block (lines 172-175), it only closes the classloader if Actually: This is correctly handled! The finally block will close the classloader. Good job on the fix in commit 86cc690. 3. Configuration Parsing Could Be More Robust (PluginManager.java:65-79)The
Recommendation: Add validation and logging: for (final String p : pluginEntries) {
final String trimmed = p.trim();
if (trimmed.isEmpty()) continue;
final String[] pluginPair = trimmed.split(":");
if (pluginPair.length == 0 || pluginPair.length > 2) {
LogManager.instance().log(this, Level.WARNING,
"Malformed plugin configuration entry: %s", trimmed);
continue;
}
// ... rest of the logic
}Moderate Issues4. assembly/full.xml Has Exclude Instead of Include (package/src/main/assembly/full.xml)In the diff, the plugin dependencySet shows Expected: <dependencySet>
<outputDirectory>lib/plugins</outputDirectory>
<includes>
<include>com.arcadedb:arcadedb-gremlin</include>
<include>com.arcadedb:arcadedb-postgresw</include>
<!-- etc -->
</includes>
<useTransitiveDependencies>false</useTransitiveDependencies>
</dependencySet>Please verify this is correct in the actual file. 5. PLUGINS.md Has Outdated Enum Name (PLUGINS.md:103)Line 103 shows return INSTALLATION_PRIORITY.AFTER_HTTP_ON; // ✗ WrongShould be: return PluginInstallationPriority.AFTER_HTTP_ON; // ✓ Correct6. Missing Integration TestsWhile unit tests are comprehensive, I don't see integration tests that verify:
Recommendation: Add at least one integration test in the e2e module that:
Minor Issues7. Code Formatting - Wildcard Imports Removed (LocalDatabase.java, ArcadeDBServer.java)The changes remove wildcard imports and expand them explicitly. This is good for clarity, but it seems unrelated to the plugin architecture feature. Recommendation: Consider keeping formatting changes separate from feature PRs for easier review. However, this is a very minor point. 8. Commented Code Left in ArcadeDBServer (commits show removed commented code)I can see from the commit history that commented-out code was removed (like 🛡️ SecurityPositive
Concerns
🚀 Performance
Minor concern: Each plugin gets its own classloader, which increases memory overhead. This is acceptable for the isolation benefits, but should be documented. 📋 Test CoverageWell Covered
Missing
📝 RecommendationsMust Fix
Should Fix
Nice to Have
🎯 ConclusionThis is a high-quality PR that significantly improves ArcadeDB's architecture. The isolated classloader design is exactly the right approach for a plugin system, and the implementation is thoughtful and well-tested. The issues identified are mostly minor, with the exception of verifying the assembly configuration and updating the documentation. Once those are addressed, this is ready to merge. Rating: ⭐⭐⭐⭐½ (4.5/5) Great work on this architectural improvement! 🎉 Review generated with attention to ArcadeDB's CLAUDE.md guidelines, focusing on code quality, architecture, testing, and security. |
Code Review: Plugin Architecture with Isolated Class LoadersSummaryThis PR introduces a sophisticated plugin architecture for ArcadeDB with isolated class loaders, allowing plugins to have different versions of dependencies without conflicts. This is a significant architectural improvement that addresses issue #157. Positive Aspects ✅1. Excellent Architecture Design
2. Strong Resource Management
3. Comprehensive Test Coverage
4. Documentation
Issues & Concerns
|
03da450 to
d1a6797
Compare
Code Review - PR #3260: Plugin Architecture with Isolated Class LoadersThis PR introduces a significant architectural improvement to ArcadeDB by implementing a plugin system with isolated class loaders. The implementation is well-structured and thoroughly tested. However, I have identified several important issues that need attention. Critical Issues1. Query Engine Compatibility Issue - Main Classloader RequirementLocation: Last commit message states "plugins on main classloader waiting for a solutions of query engine" The Problem:
Impact:
Recommendation:
High Priority Issues2. Unused Field in PluginManagerLocation: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:54 The 3. Thread Safety in Plugin StartupLocation: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:181-216 The context classloader is correctly saved/restored per-thread, but if multiple threads call 4. Test Coverage for OrderTestPluginsLocation: server/src/test/java/com/arcadedb/server/plugin/PluginManagerTest.java:396-436 Three Positive Observations5. Excellent Error HandlingThe error handling throughout is comprehensive, with proper logging at appropriate levels and meaningful exception messages including priority level (line 213). 6. Good Class Loader Design
7. Comprehensive Test CoverageThe 8. Documentation Quality
Security Considerations9. Plugin JAR ValidationThere is no verification that JAR files are signed or come from trusted sources. Malicious JARs could execute arbitrary code. Consider documentation warning about only loading trusted plugins. 10. Configuration InjectionPlugins receive full Testing RecommendationsAccording to CLAUDE.md, you should run verification tests: cd server && mvn test -Dtest=PluginManagerTest
cd gremlin && mvn test
cd postgresw && mvn test
cd mongodbw && mvn testRecommendations Before Merge
SummaryThis is a well-architected solution with excellent code quality, comprehensive tests, and good documentation. However, the critical query engine compatibility issue must be resolved before merging. The current workaround of loading plugins on the main classloader defeats the entire purpose of this PR. The architecture is sound, but the implementation needs to be completed to actually achieve isolated classloading for query engine plugins. |
- Add PluginClassLoader for isolated plugin class loading - Add PluginManager for ServiceLoader-based plugin discovery - Add PluginDescriptor for plugin metadata and lifecycle management - Update ArcadeDBServer to use PluginManager alongside legacy loading - Create META-INF/services files for all plugins (gremlin, postgresw, mongodbw, redisw, grpcw) - Update Maven assembly to deploy plugins to lib/plugins/ directory - Add comprehensive documentation (PLUGINS.md and package-info.java) - Add unit tests for PluginManager Each plugin now runs in its own class loader, allowing: - Different versions of dependencies per plugin - Shared server API classes for consistency - Standard Java ServiceLoader pattern for discovery - Clean plugin lifecycle management Resolves #157 Co-authored-by: Roberto Franchini <[email protected]>
- Add test plugin implementations for various scenarios - Add tests for successful plugin loading and lifecycle - Add tests for error scenarios (missing META-INF/services, startup failures) - Add tests for class loader isolation verification - Add tests for plugin start/stop ordering by priority - Add tests for multiple plugin loading - Use @tempdir for proper test isolation Coverage now includes: - Plugin discovery with empty/missing directories - Plugin loading with proper META-INF/services - Full lifecycle (configure -> start -> stop) - Priority-based initialization (BEFORE_HTTP_ON, AFTER_HTTP_ON) - Reverse-order shutdown - Class loader isolation between plugins - Error handling for invalid plugins Co-authored-by: Roberto Franchini <[email protected]>
…older structure Apply the same lib/plugins/ structure from full.xml to all assembly configurations: - minimal.xml: Move postgresw to lib/plugins/ with useTransitiveDependencies=false - headless.xml: Move postgresw to lib/plugins/ with useTransitiveDependencies=false - base.xml: Update comment to match new convention (base excludes all plugins) This ensures consistent plugin deployment across all distribution types. Co-authored-by: Roberto Franchini <[email protected]>
- Add creation of lib/plugins/ directory during build - Route shaded modules (plugins) to lib/plugins/ instead of lib/ - Regular modules continue to deploy to lib/ - Aligns builder with new plugin architecture using isolated class loaders Co-authored-by: Roberto Franchini <[email protected]>
- Fix resource leak in loadPlugin(): use try-finally to ensure classloader is closed when plugin is not registered - Fix duplicate plugin handling: close classloader and break loop instead of continuing iteration - Fix bolt plugin META-INF path: move service descriptor to correct location META-INF/services/com.arcadedb.server.ServerPlugin - Improve error message: include priority level in exception for better debugging Co-Authored-By: Claude Opus 4.5 <[email protected]>
d1a6797 to
b01edad
Compare
PR Review: Plugin Architecture with Isolated Class LoadersThank you for this significant architectural enhancement! This PR introduces a well-designed plugin system with isolated class loaders. Below is my detailed review. 🎯 Overall AssessmentThis is a solid architectural improvement that addresses issue #157. The implementation follows good software engineering practices with proper abstraction, lifecycle management, and comprehensive testing. However, I've identified several important issues that should be addressed before merging. ✅ Strengths1. Excellent Architecture
2. Strong Documentation
3. Good Test Coverage
4. Proper Resource Management
🔴 Critical Issues1. Incomplete Isolated Class Loading ImplementationLocation: Last commit message: "plugins on main classloader waiting for a solutions of query engine" Issue: The final commit appears to have reverted the isolated class loader functionality, moving plugins back to the main class loader. This defeats the entire purpose of the PR! Impact:
Recommendation: Either:
2. Missing Final Keyword on ParametersLocations: Throughout the codebase
Per CLAUDE.md: "use the final keyword when possible on variables and parameters"
|
Code Review: Plugin Architecture with Isolated Class LoadersThank you for this comprehensive implementation! This is a well-designed solution that brings significant value to ArcadeDB. Summary
🔴 Critical Issues (Must Fix Before Merge)1. Resource Leak in PluginManager.loadPlugin()File: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java:129-176 The finally block may not properly close classloaders in all exception scenarios. If ServiceLoader throws during iteration, the registered flag may not accurately reflect whether the classloader should be kept open. 2. Missing final Keyword (Code Style)File: server/src/main/java/com/arcadedb/server/plugin/PluginManager.java Per CLAUDE.md guidelines, variables should use final keyword when not reassigned. Lines 66, 130-131, 136-138, 149, 197-198, 236-237 need updates. 3. Inconsistent Plugin Configuration FilteringLines: 86-87 vs 158-159 Main classloader and JAR plugin filtering use different logic. JAR version checks pluginName while main classloader doesn't. 4. Commented-Out CodeFile: server/src/main/java/com/arcadedb/server/ArcadeDBServer.java:185 Remove or document the commented registerPlugins() line. 🟡 High Priority Issues5. Missing Null ValidationPluginDescriptor.setPluginInstance() should validate or document null acceptance. 6. Thread Safety DocumentationstopPlugins() uses non-thread-safe LinkedHashMap. Document that it's only called during shutdown. 7. Test Coverage for Stop OrderOrderTestPlugin classes define stopCounter but no test verifies reverse stop order. 8. Fragile JAR Name ParsingLine 131 substring fails if JAR name lacks dot. Add validation. ✅ Excellent Aspects
🎯 RecommendationsMust fix before merge: Issues 1-4 ConclusionThis is a well-architected plugin system that enhances ArcadeDB's modularity significantly. The isolated classloader approach is exactly right for preventing dependency conflicts. With the critical issues addressed, this will be a solid foundation. Great work! 🚀 |
Coverage summary from CodacySee diff coverage on Codacy
Coverage variation details
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: Diff coverage details
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: See your quality gate settings Change summary preferences |
Pull Request Review: Plugin Architecture with Isolated Class LoadersOverall Assessment: Approve with Minor Changes ⭐⭐⭐⭐This is a well-architected implementation of plugin isolation using custom class loaders. The code quality is high, documentation is excellent, and the design properly addresses the goal of allowing plugins to have different dependency versions. Critical Issues to Address1. Plugin Configuration Matching Logic Issue (Medium-High Priority)Location: The configuration check uses three different matching strategies which can lead to plugins not being loaded: if (configuredPlugins.contains(name) ||
configuredPlugins.contains(pluginName) ||
configuredPlugins.contains(pluginInstance.getClass().getName())) {Problem: The Recommendation:
2. Missing Input Validation (Medium Priority)Location: The final String[] pluginPair = p.split(":");
final String pluginName = pluginPair[0]; // Could be empty string
configuredPlugins.add(pluginName);Recommendation: Add validation: final String trimmedPlugin = p.trim();
if (trimmedPlugin.isEmpty()) continue;3. Unused classLoaderMap (Low-Medium Priority)Location: The classLoaderMap.put(classLoader, descriptor); // Never usedRecommendation: Either remove it or document its intended use case (e.g., reverse lookup from ClassLoader to PluginDescriptor). Security Considerations4. No JAR Signature Verification (Informational)The plugin loading mechanism doesn't verify JAR signatures. Malicious JARs in Recommendations:
5. Full Server Access (By Design)Plugins receive the full Test Coverage Gaps6. Unused Test InfrastructureLocation: Three public static class OrderTestPlugin1 implements ServerPlugin {
public static final AtomicInteger stopOrder = new AtomicInteger(0);
// ... but no test uses these
}Recommendation: Add a test verifying plugins stop in reverse order, or remove unused code. 7. Missing Test ScenariosConsider adding tests for:
Code Quality Improvements8. Inconsistent Error HandlingLocation: Plugin stop uses 9. Missing toString() EnhancementLocation: Adding the plugin name to public String toString() {
return "PluginClassLoader{plugin=" + pluginName + ", urls=" +
java.util.Arrays.toString(getURLs()) + "}";
}Performance Considerations10. ServiceLoader Eager InstantiationLocation: ServiceLoader instantiates all plugin classes before checking configuration: for (ServerPlugin pluginInstance : serviceLoader) {
// Plugin already instantiated here
if (configuredPlugins.contains(name) || ...) {Recommendation: Document that plugin constructors should be lightweight. Resource allocation should happen in Documentation Issues11. PLUGINS.md Example InconsistencyLine 103: Shows 12. Missing Documentation
Positive Highlights ✨
RecommendationsMust Fix Before Merge
Should Fix Before Merge
Consider for Follow-up
Summary
Overall: This implementation provides real value for dependency isolation and extensibility. The architecture is sound with proper separation of concerns. Address the configuration matching logic and input validation issues, then this is ready to merge. Excellent work! 🎉 |
Code Review: PR #3260 - Plugin Architecture with Isolated Class LoadersThank you for this well-structured implementation! The overall design is solid with good separation of concerns and comprehensive documentation. However, several issues should be addressed before merging. Critical Issues1. Resource Leak in Duplicate Plugin DetectionFile: PluginManager.java:155 When a duplicate plugin is detected, the code breaks without proper cleanup context. The comment suggests the finally block handles cleanup, but the logic relies on registered being false. This creates confusion about the control flow. Fix: Make the cleanup logic explicit or add a dedicated cleanup flag. 2. Thread Safety Issue in Plugin CollectionsFile: PluginManager.java:53-54 Mixed use of thread-safe and non-thread-safe collections. The plugins LinkedHashMap is accessed from multiple public methods without synchronization. Fix: Use Collections.synchronizedMap or add explicit synchronization. 3. Missing Null Check for JAR Name ParsingFile: PluginManager.java:131 If a JAR file does not contain a period, this will throw StringIndexOutOfBoundsException. Validate before substring operation. Security Concerns4. Broad Class Loader DelegationFile: PluginClassLoader.java:46 All com.arcadedb.* classes delegate to parent. This is very broad - includes engine internals, query parsers, serialization, etc. Consider narrowing to com.arcadedb.server.* and documenting the rationale. 5. Unrestricted Plugin Loading PathFile: PluginManager.java:60 The plugins directory path lacks validation. Recommend validating the path is within expected bounds and using Path.normalize() to prevent path traversal. Code Quality Issues6. Inconsistent Plugin Name ResolutionFiles: PluginManager.java:86, 146 Main classloader plugins use getClass().getSimpleName() while isolated plugins use getName(). Use pluginInstance.getName() consistently. 7. No Validation Logging for Empty ServiceLoaderFile: PluginManager.java:141-170 When ServiceLoader finds no plugins in a JAR, the code silently succeeds. Add logging to help with debugging. 8. Configuration Format Documentation UnclearFile: PLUGINS.md:272-275 The configuration format is not clearly explained. Documentation Issues9. Wrong Enum Name in ExampleFile: PLUGINS.md:103 Documentation shows INSTALLATION_PRIORITY.AFTER_HTTP_ON but should be PluginInstallationPriority.AFTER_HTTP_ON. Testing Gaps10. Missing Edge Case Tests
Positive Aspects
RecommendationsMust Fix Before Merge:
Should Fix Before Merge: Consider for Future: Overall AssessmentThis is a well-designed and necessary feature for ArcadeDB. The implementation demonstrates good software engineering practices. However, the critical issues should be resolved before merging to ensure production readiness. Great work on the comprehensive documentation and test coverage! |
Each plugin now runs in its own class loader, allowing:
Resolves #157
What does this PR do?
A brief description of the change being made with this pull request.
Motivation
What inspired you to submit this pull request?
Related issues
A list of issues either fixed, containing architectural discussions, otherwise relevant
for this Pull Request.
Additional Notes
Anything else we should know when reviewing?
Checklist
mvn clean packagecommand