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

[issue#255] feat: release log4j resource after biz uninstall #212

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

pzw1004
Copy link

@pzw1004 pzw1004 commented Nov 24, 2024

  1. A releaser interface has been added to prevent similar issues to Log4j from occurring in the future.
  2. Unit tests have been added.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new dependencies for enhanced logging capabilities and concurrent programming.
    • Added resource release handling during module uninstallation.
    • Implemented a new logging resource releaser for Log4j2.
  • Bug Fixes

    • Improved indentation and clarity in the event handler registration process.
  • Tests

    • Added tests for the new resource release functionality to ensure proper behavior.
  • Documentation

    • Included a new XML configuration file for Log4j2, detailing the logging setup and performance optimizations.

Copy link
Contributor

coderabbitai bot commented Nov 24, 2024

Walkthrough

The pull request introduces several changes to the koupleless-base-plugin project, including the addition of new dependencies in the pom.xml file for log4j-core and disruptor. Modifications are made to the ServerlessRuntimeActivator class, including the registration of a new event handler for resource release on uninstallation. New classes for resource releasing and handling events are introduced, alongside a test class to validate the functionality of the new event handler. Additionally, a Log4j2 configuration file is added to define the logging setup.

Changes

File Change Summary
koupleless-base-plugin/pom.xml Added dependencies for log4j-core (no version specified) and disruptor (version 3.4.2).
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java Adjusted indentation in registerEventHandler method; updated comments; registered new event handler ResourceReleaseOnUninstallEventHandler.
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResourceReleaseOnUninstallEventHandler.java Introduced new class ResourceReleaseOnUninstallEventHandler implementing EventHandler for resource management. Added methods for handling events and priority.
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/Log4jResourceReleaser.java Added class Log4jResourceReleaser implementing ResourceReleaser for releasing Log4j2 resources.
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/ResourceReleaser.java Introduced interface ResourceReleaser with method doRelease(Biz biz) for resource management.
koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResouceReleaseOnUninstallEventHandlerTest.java Added test class ResouceReleaseOnUninstallEventHandlerTest to validate functionality of the new event handler.
koupleless-base-plugin/src/test/resources/log4j2-test.xml Added Log4j2 configuration file defining logging setup with various appenders and asynchronous logging.

Possibly related PRs

🐇 "In the garden of code, we hop with glee,
New tools for our tasks, oh what a spree!
With loggers and handlers, our work's a delight,
Resource release magic, all shining so bright!
So let's dance through the lines, with joy we declare,
In the world of plugins, we flourish with care!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (9)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/ResourceReleaser.java (1)

5-11: Enhance interface documentation for better clarity.

While the basic documentation is present, consider adding more detailed documentation to:

  • Explain the interface's role in resource release lifecycle
  • Specify contract requirements for implementers
  • Document the relationship with business uninstallation
  • Add @since tag for version tracking
 /**
  * @author: poorpaper
  * @time: 2024/11/24 00:16
+ * @since 1.0.0
  *
  * 通用资源释放接口
+ * 
+ * This interface defines a contract for releasing resources associated with a Biz instance
+ * during the uninstallation process. Implementations should handle cleanup of specific
+ * resources (e.g., Log4j resources) in a thread-safe manner.
+ * 
+ * Implementing classes must ensure that:
+ * - Resource release is idempotent
+ * - All exceptions are properly handled
+ * - No resources are left in an inconsistent state
  */
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/Log4jResourceReleaser.java (3)

15-20: Enhance class documentation with implementation details

The current documentation is in Chinese and lacks crucial implementation details. Consider adding:

  • English documentation
  • Thread safety guarantees
  • Explanation of the 300ms timeout value
  • Potential impact of resource cleanup on the system
 /**
- * @author: poorpaper
- * @time: 2024/11/24 00:27
- *
- * Log4j2资源释放器
+ * Log4j2 resource releaser responsible for cleaning up logger contexts
+ * associated with uninstalled business modules.
+ *
+ * @author poorpaper
+ * @since 2024/11/24
+ *
+ * @implNote This implementation uses a 300ms timeout when stopping logger contexts.
+ * The cleanup process is thread-safe as Log4j2's internal synchronization is utilized.
+ * If cleanup fails, it will be logged but won't block the uninstallation process.
  */

32-33: Consider more robust context naming strategy

Using identityHashCode alone might lead to naming conflicts in rare cases. Consider incorporating additional unique identifiers from the Biz instance.

-                String ctxName = Integer.toHexString(System.identityHashCode(bizClassLoader));
+                String ctxName = String.format("%s_%s_%s",
+                    biz.getBizName(),
+                    biz.getBizVersion(),
+                    Integer.toHexString(System.identityHashCode(bizClassLoader)));

42-45: Refine error handling approach

The current error handling catches Throwable which is too broad and the error message could be more specific.

-        } catch (Throwable t) {
-            LOGGER.error(String.format("[Log4jResourceReleaser] Error happened when clean biz %s log4j cache",
-                    biz.getIdentity()), t);
+        } catch (Exception e) {
+            LOGGER.error("[Log4jResourceReleaser] Failed to cleanup Log4j context for biz {}: {}",
+                    biz.getIdentity(), e.getMessage(), e);
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (1)

47-50: Consider using consistent language in comments.

The comment mixes Chinese and English. Consider using a single language throughout the codebase for better maintainability and international collaboration.

Suggested English version:

- // 清理用户主动托管给 Serverless 运行时的 ExecutorService (含线程池), Timer, Thread 与 资源(如Log4jContext).
+ // Cleanup of user-managed resources in Serverless runtime: ExecutorService (including thread pools), Timer, Thread, and resources (e.g., Log4jContext).
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResourceReleaseOnUninstallEventHandler.java (1)

57-60: Document priority selection rationale

The handler is set to LOWEST_PRECEDENCE without explanation. This could be problematic if:

  1. Other cleanup handlers depend on these resources
  2. Order of cleanup operations is critical

Please add documentation explaining:

  1. Why lowest priority was chosen
  2. Any dependencies or ordering requirements
     @Override
+    /**
+     * Returns the handler's priority level.
+     * Set to LOWEST_PRECEDENCE because [explain rationale here]
+     * @return The priority level for this event handler
+     */
     public int getPriority() {
         return LOWEST_PRECEDENCE;
     }
koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResouceReleaseOnUninstallEventHandlerTest.java (3)

23-23: Fix typo in class name: "Resouce" should be "Resource"

The class name contains a spelling error that should be corrected for consistency and clarity.

-public class ResouceReleaseOnUninstallEventHandlerTest {
+public class ResourceReleaseOnUninstallEventHandlerTest {

35-36: Consider moving Log4j configuration to a test properties file

Setting system properties directly in the test code makes it harder to maintain and reuse configurations. Consider moving these to a test configuration file.


48-50: Simplify LoggerContext startup logic

The initialization check before starting the context seems redundant. The start() method is idempotent and can be called directly.

-        if (testLoggerContext.isInitialized()) {
-            testLoggerContext.start();
-        }
+        testLoggerContext.start();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between be061b7 and 58232c4.

📒 Files selected for processing (7)
  • koupleless-base-plugin/pom.xml (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResourceReleaseOnUninstallEventHandler.java (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/Log4jResourceReleaser.java (1 hunks)
  • koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/ResourceReleaser.java (1 hunks)
  • koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResouceReleaseOnUninstallEventHandlerTest.java (1 hunks)
  • koupleless-base-plugin/src/test/resources/log4j2-test.xml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • koupleless-base-plugin/src/test/resources/log4j2-test.xml
🔇 Additional comments (9)
koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/ResourceReleaser.java (2)

1-4: LGTM! Package structure and imports are appropriate.

The package placement in the handler hierarchy and the import of Biz are well-organized.


12-20: 🛠️ Refactor suggestion

Improve method documentation and consider error handling.

The method signature is clean, but consider these improvements:

  1. Enhance method documentation
  2. Specify error handling expectations
  3. Consider adding a return value to indicate success/failure
 public interface ResourceReleaser {
     /**
-     * 获取待释放资源上下文
+     * Releases resources associated with the given Biz instance.
      *
-     * @param biz
+     * @param biz The business module whose resources need to be released
+     * @throws IllegalArgumentException if biz is null
+     * @throws ResourceReleaseException if resource release fails
+     * @return boolean indicating whether the release was successful
      */
-    void doRelease(Biz biz);
+    boolean doRelease(Biz biz);
 }

Let's verify if any implementations already exist:

koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/releaser/Log4jResourceReleaser.java (1)

22-47: Verify integration with event system

The implementation looks solid, but we should verify its integration with the event system.

✅ Verification successful

Integration with event system is properly implemented

The Log4j resource releaser is well integrated into the event system:

  • Automatically registered in ResourceReleaseOnUninstallEventHandler constructor
  • Handler is registered with event admin service in ServerlessRuntimeActivator
  • Executed during the BeforeBizRecycleEvent event handling
  • Stored in a thread-safe concurrent map (BIZ_CLASS_LOADER_TO_RELEASER)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check how this releaser is registered and the order of execution
rg -l "ResourceReleaseOnUninstallEventHandler|ResourceReleaser" --type java

# Check for other resource releasers to understand the execution order
ast-grep --pattern 'class $_ implements ResourceReleaser'

# Look for event handler registration
rg -A 5 "ResourceReleaseOnUninstallEventHandler"

Length of output: 8133

koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java (2)

42-42: LGTM! Improved code formatting.

The indentation adjustment enhances readability by properly aligning the method chain.


51-51: Verify handler execution order.

The new ResourceReleaseOnUninstallEventHandler is registered after other cleanup handlers. Please verify that the handler's priority ensures correct resource cleanup order, especially in relation to other handlers that might depend on Log4j for logging during their cleanup.

✅ Verification successful

Let me generate another script to search for the handler implementation and its priority:

The previous search didn't reveal the handler implementations. Let's try to find the specific handler class and any priority-related code to verify the execution order.


Let me try one more search with a different pattern to locate the handler and priority-related code:

This should help us find the handler implementation and understand how priorities are managed in the event system.


Priority order is correctly implemented for resource cleanup

The ResourceReleaseOnUninstallEventHandler has LOWEST_PRECEDENCE priority, which is appropriate since:

  1. CancelTimersOnUninstallEventHandler runs first with HIGHEST_PRECEDENCE
  2. ShutdownExecutorServicesOnUninstallEventHandler runs next with HIGHEST_PRECEDENCE + 1
  3. Other handlers run with DEFAULT_PRECEDENCE
  4. Resource cleanup handlers (including Log4j) run last with LOWEST_PRECEDENCE

This ensures that Log4j resources remain available for other handlers that might need logging during their cleanup operations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the priority implementation of all handlers to ensure correct execution order

# Search for getPriority implementations in all handlers
ast-grep --pattern 'class $_ implements EventHandler {
  $$$
  getPriority() {
    $$$
  }
  $$$
}'

# Also check for any direct logging calls in other handlers
rg -A 2 "log\." --type java

Length of output: 20246


Script:

#!/bin/bash
# Search for ResourceReleaseOnUninstallEventHandler implementation
ast-grep --pattern 'class ResourceReleaseOnUninstallEventHandler {
  $$$
}'

# Also search for any class implementing EventHandler with priority
ast-grep --pattern 'class $_ implements $_ {
  $$$
  public int getPriority() {
    $$$
  }
  $$$
}'

Length of output: 180


Script:

#!/bin/bash
# Search for ResourceReleaseOnUninstallEventHandler in any context
rg -l "ResourceReleaseOnUninstallEventHandler"

# Search for the file content if found
rg -A 10 "class ResourceReleaseOnUninstallEventHandler"

# Search for any priority-related code in event handlers
rg -A 5 "getPriority|priority.*=|PRIORITY" --type java

Length of output: 12395

koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResourceReleaseOnUninstallEventHandler.java (1)

1-61: Verify integration with Log4jResourceReleaser and usage

Please ensure:

  1. The Log4jResourceReleaser implementation properly handles all Log4j resources
  2. The handler is properly registered in the event system
✅ Verification successful

Let me gather more information about the Log4jResourceReleaser implementation.


Integration verified and properly implemented

The verification confirms:

  1. Log4jResourceReleaser correctly implements the ResourceReleaser interface and properly handles Log4j resources by:

    • Safely stopping logger contexts associated with the module's ClassLoader
    • Including timeout handling (300ms) and error handling
    • Properly cleaning up Log4j cache
  2. The handler is properly registered in ServerlessRuntimeActivator and tested:

    • Registered via eventAdminService.register()
    • Comprehensive test coverage verifying logger context lifecycle
    • Proper cleanup of resources during module uninstallation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Log4jResourceReleaser implementation
echo "Checking Log4jResourceReleaser implementation..."
ast-grep --pattern 'class Log4jResourceReleaser implements ResourceReleaser {
  $$$
}'

# Check handler registration
echo "Checking handler registration..."
rg -A 5 "ResourceReleaseOnUninstallEventHandler"

Length of output: 7657


Script:

#!/bin/bash
# Check Log4jResourceReleaser implementation
echo "Checking Log4jResourceReleaser implementation..."
fd "Log4jResourceReleaser.java" -x cat {}

# Check ResourceReleaser interface
echo -e "\nChecking ResourceReleaser interface..."
fd "ResourceReleaser.java" -x cat {}

# Check test implementation
echo -e "\nChecking test implementation..."
fd "ResouceReleaseOnUninstallEventHandlerTest.java" -x cat {}

Length of output: 6887

koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResouceReleaseOnUninstallEventHandlerTest.java (1)

36-36: Verify log4j2-test.xml configuration file

The test depends on "log4j2-test.xml" configuration file.

✅ Verification successful

Log4j2 configuration file is present and properly formatted

The log4j2-test.xml file exists at the correct location (koupleless-base-plugin/src/test/resources/) and contains a valid XML configuration with properly defined appenders and loggers for test execution.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the log4j2-test.xml exists and validate its format
fd -t f "log4j2-test.xml" -x sh -c '
    echo "Found config file: {}"
    if command -v xmllint > /dev/null; then
        xmllint --noout "{}" && echo "XML format is valid"
    else
        cat "{}"
    fi
'

Length of output: 2047

koupleless-base-plugin/pom.xml (2)

70-74: Review Disruptor version for updates.

The Disruptor library is pinned to version 3.4.2 (from 2019). Consider updating to the latest stable version for potential performance improvements and bug fixes.

Run the following script to check for the latest version:


66-69: Specify and verify Log4j version for security.

Given that this PR focuses on Log4j resource management, it's crucial to explicitly specify and verify the Log4j version for security considerations.

Run the following script to check for security advisories and latest versions:

Comment on lines +29 to +31
ClassLoader bizClassLoader = biz.getBizClassLoader();
LoggerContextFactory factory = LogManager.getFactory();
if (factory instanceof Log4jContextFactory) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null check for bizClassLoader

The getBizClassLoader() call could potentially return null. Add a defensive check to prevent NPE.

     public void doRelease(Biz biz) {
         try {
             ClassLoader bizClassLoader = biz.getBizClassLoader();
+            if (bizClassLoader == null) {
+                LOGGER.warn("[Log4jResourceReleaser] Skipping cleanup for biz {} as classloader is null", 
+                    biz.getIdentity());
+                return;
+            }
             LoggerContextFactory factory = LogManager.getFactory();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ClassLoader bizClassLoader = biz.getBizClassLoader();
LoggerContextFactory factory = LogManager.getFactory();
if (factory instanceof Log4jContextFactory) {
ClassLoader bizClassLoader = biz.getBizClassLoader();
if (bizClassLoader == null) {
LOGGER.warn("[Log4jResourceReleaser] Skipping cleanup for biz {} as classloader is null",
biz.getIdentity());
return;
}
LoggerContextFactory factory = LogManager.getFactory();
if (factory instanceof Log4jContextFactory) {

Comment on lines +36 to +39
boolean stop = context.stop(300, TimeUnit.MILLISECONDS);
LOGGER.info("[Log4jResourceReleaser] Stop biz {}:{} logger context,result={}",
biz.getBizName(), biz.getBizVersion(), stop);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make timeout configurable and improve logging

The 300ms timeout is hardcoded and the logging could be more informative about timeout situations.

+    private static final long DEFAULT_TIMEOUT_MS = 300;
+    private final long timeoutMs;
+
+    public Log4jResourceReleaser(long timeoutMs) {
+        this.timeoutMs = timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
+    }
+
+    public Log4jResourceReleaser() {
+        this(DEFAULT_TIMEOUT_MS);
+    }

     // In doRelease method:
-                        boolean stop = context.stop(300, TimeUnit.MILLISECONDS);
+                        boolean stop = context.stop(timeoutMs, TimeUnit.MILLISECONDS);
                         LOGGER.info("[Log4jResourceReleaser] Stop biz {}:{} logger context,result={}",
-                                biz.getBizName(), biz.getBizVersion(), stop);
+                                biz.getBizName(), biz.getBizVersion(),
+                                stop ? "success" : "timed out after " + timeoutMs + "ms");

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +27 to +32
public ResourceReleaseOnUninstallEventHandler() {
ClassLoader contextClassLoader = currentThread().getContextClassLoader();
BIZ_CLASS_LOADER_TO_RELEASER.putIfAbsent(contextClassLoader, new ConcurrentHashMap<>());
// 注册资源释放器
BIZ_CLASS_LOADER_TO_RELEASER.get(contextClassLoader).putIfAbsent(Log4jResourceReleaser.class.getName(), new Log4jResourceReleaser());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve constructor robustness and extensibility

The constructor has several potential issues:

  1. Assumes the current thread's context ClassLoader is the correct one
  2. Hardcodes Log4jResourceReleaser registration
  3. Lacks error handling for ClassLoader access

Consider:

  1. Accepting ClassLoader as a parameter
  2. Implementing a plugin system for releaser registration
  3. Adding proper error handling
-    public ResourceReleaseOnUninstallEventHandler() {
-        ClassLoader contextClassLoader = currentThread().getContextClassLoader();
-        BIZ_CLASS_LOADER_TO_RELEASER.putIfAbsent(contextClassLoader, new ConcurrentHashMap<>());
-        // 注册资源释放器
-        BIZ_CLASS_LOADER_TO_RELEASER.get(contextClassLoader).putIfAbsent(Log4jResourceReleaser.class.getName(), new Log4jResourceReleaser());
+    public ResourceReleaseOnUninstallEventHandler(ClassLoader targetClassLoader) {
+        if (targetClassLoader == null) {
+            targetClassLoader = currentThread().getContextClassLoader();
+        }
+        try {
+            BIZ_CLASS_LOADER_TO_RELEASER.putIfAbsent(targetClassLoader, new ConcurrentHashMap<>());
+            registerReleasers(targetClassLoader);
+        } catch (Exception e) {
+            LOGGER.error("Failed to initialize ResourceReleaseOnUninstallEventHandler", e);
+            throw new RuntimeException("Failed to initialize resource releaser", e);
+        }
+    }
+
+    protected void registerReleasers(ClassLoader classLoader) {
+        // Register default releasers
+        BIZ_CLASS_LOADER_TO_RELEASER.get(classLoader)
+            .putIfAbsent(Log4jResourceReleaser.class.getName(), new Log4jResourceReleaser());
+        // Additional releasers can be registered here or through a plugin system
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ResourceReleaseOnUninstallEventHandler() {
ClassLoader contextClassLoader = currentThread().getContextClassLoader();
BIZ_CLASS_LOADER_TO_RELEASER.putIfAbsent(contextClassLoader, new ConcurrentHashMap<>());
// 注册资源释放器
BIZ_CLASS_LOADER_TO_RELEASER.get(contextClassLoader).putIfAbsent(Log4jResourceReleaser.class.getName(), new Log4jResourceReleaser());
}
public ResourceReleaseOnUninstallEventHandler(ClassLoader targetClassLoader) {
if (targetClassLoader == null) {
targetClassLoader = currentThread().getContextClassLoader();
}
try {
BIZ_CLASS_LOADER_TO_RELEASER.putIfAbsent(targetClassLoader, new ConcurrentHashMap<>());
registerReleasers(targetClassLoader);
} catch (Exception e) {
LOGGER.error("Failed to initialize ResourceReleaseOnUninstallEventHandler", e);
throw new RuntimeException("Failed to initialize resource releaser", e);
}
}
protected void registerReleasers(ClassLoader classLoader) {
// Register default releasers
BIZ_CLASS_LOADER_TO_RELEASER.get(classLoader)
.putIfAbsent(Log4jResourceReleaser.class.getName(), new Log4jResourceReleaser());
// Additional releasers can be registered here or through a plugin system
}

Comment on lines +34 to +55
@Override
public void handleEvent(BeforeBizRecycleEvent event) {
ClassLoader bizClassLoader = event.getSource().getBizClassLoader();
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] Module name: {} , BizClassLoader: {} .",
event.getSource().getBizName(), bizClassLoader);

ConcurrentHashMap<String, ResourceReleaser> releasers = BIZ_CLASS_LOADER_TO_RELEASER.get(bizClassLoader);
if (releasers == null) {
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] No releasers found for module: {} , just return. ",
event.getSource().getBizName());
return;
}

LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] {} releasers found for module: {} . ",
releasers.size(), event.getSource().getBizName());
for (ResourceReleaser releaser : releasers.values()) {
releaser.doRelease(event.getSource());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling and map cleanup in event handler

The event handling logic needs improvement in several areas:

  1. Missing error handling during releaser execution
  2. No cleanup of the ClassLoader entry from the static map
  3. No handling of partial failures during release
     @Override
     public void handleEvent(BeforeBizRecycleEvent event) {
         ClassLoader bizClassLoader = event.getSource().getBizClassLoader();
         LOGGER.info(
                 "[ResourceReleaseOnUninstallEventHandler] Module name: {} , BizClassLoader: {} .",
                 event.getSource().getBizName(), bizClassLoader);
 
         ConcurrentHashMap<String, ResourceReleaser> releasers = BIZ_CLASS_LOADER_TO_RELEASER.get(bizClassLoader);
         if (releasers == null) {
             LOGGER.info(
                     "[ResourceReleaseOnUninstallEventHandler] No releasers found for module: {} , just return. ",
                     event.getSource().getBizName());
             return;
         }
 
         LOGGER.info(
                 "[ResourceReleaseOnUninstallEventHandler] {} releasers found for module: {} . ",
                 releasers.size(), event.getSource().getBizName());
+        boolean hasErrors = false;
         for (ResourceReleaser releaser : releasers.values()) {
+            try {
                 releaser.doRelease(event.getSource());
+            } catch (Exception e) {
+                hasErrors = true;
+                LOGGER.error("Failed to release resources for {} using {}", 
+                    event.getSource().getBizName(), releaser.getClass().getName(), e);
+            }
         }
+        
+        // Clean up the map entry
+        BIZ_CLASS_LOADER_TO_RELEASER.remove(bizClassLoader);
+        
+        if (hasErrors) {
+            throw new RuntimeException("Some resources failed to release properly. Check logs for details.");
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void handleEvent(BeforeBizRecycleEvent event) {
ClassLoader bizClassLoader = event.getSource().getBizClassLoader();
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] Module name: {} , BizClassLoader: {} .",
event.getSource().getBizName(), bizClassLoader);
ConcurrentHashMap<String, ResourceReleaser> releasers = BIZ_CLASS_LOADER_TO_RELEASER.get(bizClassLoader);
if (releasers == null) {
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] No releasers found for module: {} , just return. ",
event.getSource().getBizName());
return;
}
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] {} releasers found for module: {} . ",
releasers.size(), event.getSource().getBizName());
for (ResourceReleaser releaser : releasers.values()) {
releaser.doRelease(event.getSource());
}
}
@Override
public void handleEvent(BeforeBizRecycleEvent event) {
ClassLoader bizClassLoader = event.getSource().getBizClassLoader();
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] Module name: {} , BizClassLoader: {} .",
event.getSource().getBizName(), bizClassLoader);
ConcurrentHashMap<String, ResourceReleaser> releasers = BIZ_CLASS_LOADER_TO_RELEASER.get(bizClassLoader);
if (releasers == null) {
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] No releasers found for module: {} , just return. ",
event.getSource().getBizName());
return;
}
LOGGER.info(
"[ResourceReleaseOnUninstallEventHandler] {} releasers found for module: {} . ",
releasers.size(), event.getSource().getBizName());
boolean hasErrors = false;
for (ResourceReleaser releaser : releasers.values()) {
try {
releaser.doRelease(event.getSource());
} catch (Exception e) {
hasErrors = true;
LOGGER.error("Failed to release resources for {} using {}",
event.getSource().getBizName(), releaser.getClass().getName(), e);
}
}
// Clean up the map entry
BIZ_CLASS_LOADER_TO_RELEASER.remove(bizClassLoader);
if (hasErrors) {
throw new RuntimeException("Some resources failed to release properly. Check logs for details.");
}
}

Comment on lines +24 to +25
static final ConcurrentHashMap<ClassLoader, ConcurrentHashMap<String, ResourceReleaser>> BIZ_CLASS_LOADER_TO_RELEASER =
new ConcurrentHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Memory leak concern is valid - map entries are not removed after use

After reviewing the implementation, the concern about memory leaks is confirmed. The handleEvent method retrieves and uses the releasers from BIZ_CLASS_LOADER_TO_RELEASER but never removes the entries after module uninstallation. This will cause the map to retain references to ClassLoaders of uninstalled modules.

Key issues:

  • No cleanup of BIZ_CLASS_LOADER_TO_RELEASER entries after doRelease is called
  • ClassLoader references are held indefinitely in the static map
  • The test case verifies resource release but doesn't verify map cleanup

Recommendation: Add BIZ_CLASS_LOADER_TO_RELEASER.remove(bizClassLoader) after the release loop in handleEvent method.

🔗 Analysis chain

Consider adding cleanup mechanism for static map

The static BIZ_CLASS_LOADER_TO_RELEASER map could potentially lead to memory leaks if ClassLoader entries aren't removed after module uninstallation. Consider:

  1. Adding a cleanup mechanism in the event handler
  2. Using WeakHashMap or maintaining explicit lifecycle management
  3. Adding documentation about thread-safety guarantees
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there's any cleanup mechanism in other handlers
rg -l "BIZ_CLASS_LOADER_TO_RELEASER\.remove" 

Length of output: 46


Script:

#!/bin/bash
# Let's check the implementation of the event handler to see how the map is managed
ast-grep --pattern 'class ResourceReleaseOnUninstallEventHandler {
  $$$
  public void handle($_) {
    $$$
  }
  $$$
}'

# Also check for any clear/remove operations on the map
rg -A 5 "BIZ_CLASS_LOADER_TO_RELEASER" 

# Check if there are any weak references used
rg "WeakReference|WeakHashMap" 

Length of output: 3759


Script:

#!/bin/bash
# Let's check the complete event handler implementation to see if cleanup happens during uninstallation
ast-grep --pattern 'public void handleEvent(BeforeBizRecycleEvent event) {
  $$$
}'

# Also check for any other methods in this class that might handle cleanup
ast-grep --pattern 'class ResourceReleaseOnUninstallEventHandler {
  $$$
  private void $_($_) {
    $$$
  }
  $$$
}'

# Check if there are any other classes that might access and clean this map
rg -l "ResourceReleaseOnUninstallEventHandler"

Length of output: 620


Script:

#!/bin/bash
# Let's check the test file to understand the expected behavior
cat koupleless-base-plugin/src/test/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResouceReleaseOnUninstallEventHandlerTest.java

# Check the activator to see how this handler is used
rg -A 5 "ResourceReleaseOnUninstallEventHandler" koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/ServerlessRuntimeActivator.java

Length of output: 2955


Script:

#!/bin/bash
# Let's check the complete implementation of handleEvent method to understand the cleanup logic
cat koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/manager/handler/ResourceReleaseOnUninstallEventHandler.java

# Also check the ResourceReleaser interface to understand the cleanup contract
rg -l "interface.*ResourceReleaser"

Length of output: 2767


generateTestLoggerContextFactory();

assertEquals(true, testLoggerContext.isStarted() || testLoggerContext.isStarting());
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test assertions and add error scenarios

The current test could be enhanced in several ways:

  1. Use more expressive assertions (e.g., assertThat() with descriptive matchers)
  2. Add test cases for error scenarios
  3. Clean up system properties after the test
-        assertEquals(true, testLoggerContext.isStarted() || testLoggerContext.isStarting());
+        assertThat("Logger context should be active before event handling",
+                   testLoggerContext.isStarted() || testLoggerContext.isStarting(),
+                   is(true));

-        assertEquals(true, testLoggerContext.isStopped() || testLoggerContext.isStopping());
+        assertThat("Logger context should be inactive after event handling",
+                   testLoggerContext.isStopped() || testLoggerContext.isStopping(),
+                   is(true));

Also, consider adding these test cases:

@Test
public void testHandleReleaseWithNullEvent() {
    assertThrows(IllegalArgumentException.class, () -> 
        new ResourceReleaseOnUninstallEventHandler().handleEvent(null));
}

@Test
public void testHandleReleaseWithNullBizModel() {
    BeforeBizRecycleEvent event = new BeforeBizRecycleEvent(null);
    assertThrows(IllegalArgumentException.class, () -> 
        new ResourceReleaseOnUninstallEventHandler().handleEvent(event));
}

Also applies to: 70-70

Comment on lines +54 to +56
public void tearDown() {
currentThread().setContextClassLoader(originalClassLoader);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Clean up system properties in tearDown

The test sets system properties but doesn't clean them up, which could affect other tests.

 @After
 public void tearDown() {
     currentThread().setContextClassLoader(originalClassLoader);
+    System.clearProperty("log4j2.loggerContextFactory");
+    System.clearProperty("log4j.configurationFile");
+    if (testLoggerContext != null) {
+        testLoggerContext.stop();
+    }
 }

Committable suggestion skipped: line range outside the PR's diff.

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.

1 participant