Skip to content

Fix StandardSofaRuntimeManager mem leak #1373

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

Merged
merged 1 commit into from
Jun 3, 2025

Conversation

CrazyHZM
Copy link
Contributor

@CrazyHZM CrazyHZM commented May 26, 2025

Summary by CodeRabbit

  • New Features

    • Added a standardized method to release all stored client instances, improving resource cleanup during shutdown procedures.
  • Bug Fixes

    • Enhanced shutdown process to ensure client resources are properly released, reducing the risk of resource leaks.

@sofastack-cla sofastack-cla bot added bug Something isn't working cla:yes size/XS labels May 26, 2025
Copy link

coderabbitai bot commented May 26, 2025

Walkthrough

A new destroy() method was added to the ClientFactoryInternal interface and implemented in ClientFactoryImpl to clear internal client resources. The StandardSofaRuntimeManager now calls this method during external shutdown, ensuring client resources are released as part of the shutdown sequence.

Changes

File(s) Change Summary
.../spi/client/ClientFactoryInternal.java Added void destroy() method to the interface.
.../impl/ClientFactoryImpl.java Implemented destroy() to clear the internal clients map.
.../impl/StandardSofaRuntimeManager.java Invokes clientFactoryInternal.destroy() during external shutdown.

Sequence Diagram(s)

sequenceDiagram
    participant ExternalCaller
    participant StandardSofaRuntimeManager
    participant ClientFactoryInternal
    participant ApplicationContext

    ExternalCaller->>StandardSofaRuntimeManager: shutDownExternally()
    StandardSofaRuntimeManager->>ClientFactoryInternal: destroy()
    ClientFactoryInternal-->>StandardSofaRuntimeManager: (clients cleared)
    StandardSofaRuntimeManager->>ApplicationContext: close()
    StandardSofaRuntimeManager->>ApplicationContext: set rootApplicationContext = null
Loading

Poem

In the warren where clients dwell,
A tidy bunny rings the bell.
With destroy() called, the fields are clear,
No stray clients linger here.
When shutdown comes, we hop with glee—
Resource leaks? Not for me!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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

@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: 2

🔭 Outside diff range comments (1)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/StandardSofaRuntimeManager.java (1)

113-126: 💡 Verification agent

🧩 Analysis chain

Consider aligning shutdown behavior between methods.

The shutDownExternally() method now calls destroy() on clientFactoryInternal, but the regular shutdown() method doesn't. This inconsistency could lead to resource leaks depending on which shutdown path is taken.

Consider:

  1. Adding clientFactoryInternal.destroy() to the shutdown() method before calling clear().
  2. Calling clear() in shutDownExternally() to ensure all references are properly nullified.

Run the following script to check how these shutdown methods are used in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Verify usage patterns of shutdown methods to understand the impact

# Test 1: Find all calls to shutdown() method
echo "=== Calls to shutdown() method ==="
rg -A 3 "\.shutdown\(\)" --type java

# Test 2: Find all calls to shutDownExternally() method  
echo -e "\n=== Calls to shutDownExternally() method ==="
rg -A 3 "\.shutDownExternally\(\)" --type java

# Test 3: Check if ClientFactoryInternal has other cleanup requirements
echo -e "\n=== ClientFactoryInternal interface definition ==="
fd -e java -x grep -l "interface ClientFactoryInternal" {} \; | xargs cat

Length of output: 13179


🏁 Script executed:

#!/bin/bash
# Extract clear() implementation to verify which fields are reset
rg -A 10 "void clear" --type java sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/StandardSofaRuntimeManager.java

Length of output: 519


Align shutdown behaviors in StandardSofaRuntimeManager

To prevent resource leaks and ensure complete cleanup, both shutdown() and shutDownExternally() should:

  • Invoke clientFactoryInternal.destroy() before dropping the reference.
  • Call clear() to null out all internal fields and clear shutdown hooks.

Currently:

  • shutdown() calls clear() (which nulls clientFactoryInternal) but never invokes destroy().
  • shutDownExternally() invokes destroy() but never calls clear().

Suggested changes:

--- a/.../StandardSofaRuntimeManager.java
@@ public void shutdown() throws ServiceRuntimeException {
-    if (componentManager != null) {
-        componentManager.shutdown();
-    }
-    clear();
+    if (componentManager != null) {
+        componentManager.shutdown();
+    }
+    if (clientFactoryInternal != null) {
+        clientFactoryInternal.destroy();
+    }
+    clear();
@@ public void shutDownExternally() throws ServiceRuntimeException {
-    clientFactoryInternal.destroy();
-    AbstractApplicationContext applicationContext = (AbstractApplicationContext) rootApplicationContext;
-    if (applicationContext.isActive()) {
-        applicationContext.close();
-    }
-    rootApplicationContext = null;
-    appClassLoader = null;
+    if (clientFactoryInternal != null) {
+        clientFactoryInternal.destroy();
+    }
+    AbstractApplicationContext applicationContext = (AbstractApplicationContext) rootApplicationContext;
+    if (applicationContext.isActive()) {
+        applicationContext.close();
+    }
+    clear();

This ensures both shutdown paths first destroy the client factory, then null out all references and clear any registered shutdown hooks.

🧹 Nitpick comments (2)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/client/ClientFactoryInternal.java (1)

36-36: Add JavaDoc documentation for the new destroy() method.

For consistency with the existing code style and to clarify the method's purpose, please add JavaDoc documentation.

Apply this diff to add documentation:

+    /**
+     * Destroy the client factory and release all registered clients.
+     */
     void destroy();
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/ClientFactoryImpl.java (1)

32-46: Consider thread safety during shutdown sequence.

While ConcurrentHashMap operations are thread-safe individually, there might be race conditions if registerClient() or getClient() are called concurrently with destroy(). Consider documenting the expected shutdown behavior or adding a state check to prevent operations after destruction.

Consider implementing a shutdown state to prevent new registrations after destroy:

 public class ClientFactoryImpl implements ClientFactoryInternal {
 
     private final Map<Class<?>, Object> clients = new ConcurrentHashMap<>(8);
+    private volatile boolean destroyed = false;
 
     @Override
     public void registerClient(Class<?> clientType, Object clientInstance) {
+        if (destroyed) {
+            throw new IllegalStateException("ClientFactory has been destroyed");
+        }
         if (clients.containsKey(clientType)) {
             return;
         }
 
         clients.put(clientType, clientInstance);
     }
 
     @Override
     public void destroy() {
+        destroyed = true;
         clients.clear();
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public <T> T getClient(Class<T> clazz) {
+        if (destroyed) {
+            return null;
+        }
         return (T) clients.get(clazz);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c53e51c and 85b5bc2.

📒 Files selected for processing (3)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/ClientFactoryImpl.java (1 hunks)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/StandardSofaRuntimeManager.java (1 hunks)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/client/ClientFactoryInternal.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build (21)
  • GitHub Check: build (17)
  • GitHub Check: sca
🔇 Additional comments (2)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/StandardSofaRuntimeManager.java (1)

121-121: Good addition for preventing memory leaks!

Setting rootApplicationContext to null after closing it helps ensure proper garbage collection and prevents potential memory leaks.

sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/ClientFactoryImpl.java (1)

43-46: LGTM! The implementation correctly clears the client map.

The use of ConcurrentHashMap.clear() ensures thread-safe removal of all entries.

@@ -112,11 +112,13 @@ public void shutdown() throws ServiceRuntimeException {
@Override
public void shutDownExternally() throws ServiceRuntimeException {
try {
clientFactoryInternal.destroy();
Copy link

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 before calling destroy().

While adding the destroy() call is good for preventing memory leaks, consider adding a null check for defensive programming:

Apply this diff to add null check:

-clientFactoryInternal.destroy();
+if (clientFactoryInternal != null) {
+    clientFactoryInternal.destroy();
+}
🤖 Prompt for AI Agents
In
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/StandardSofaRuntimeManager.java
at line 115, the destroy() method is called without checking if
clientFactoryInternal is null, which could lead to a NullPointerException. Add a
null check before calling destroy() to ensure safe execution and prevent
potential runtime errors.

Comment on lines +43 to +46
@Override
public void destroy() {
clients.clear();
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider resource cleanup for client instances.

If the client instances themselves hold resources (e.g., connections, thread pools, file handles), simply clearing the map might not be sufficient. Consider whether clients need their own cleanup methods to be called before removal.

Run the following script to check if any client implementations have cleanup-related methods:


🏁 Script executed:

#!/bin/bash
# Description: Search for client implementations that might have cleanup methods

# Search for classes that might be registered as clients
ast-grep --pattern 'class $_ {
  $$$
  close($$$) {
    $$$
  }
  $$$
}'

# Also search for shutdown/destroy/cleanup methods
rg -A 3 "(void\s+(shutdown|destroy|cleanup|close|dispose)\s*\()"

Length of output: 10095


#!/bin/bash
# Display the ClientFactoryInternal interface
fd ClientFactoryInternal.java -x sed -n '1,200p' {}

# Display the ClientFactoryImpl implementation and clients field
fd ClientFactoryImpl.java -x sed -n '1,200p' {}

Enhance ClientFactoryImpl.destroy() to invoke client cleanup

Since ClientFactoryImpl simply clears the clients map, any client‐specific resources (connections, threads, etc.) won’t be released. Even though we didn’t find a uniform “cleanup” method on those instances, it’s safest to:

  • Iterate over clients.values()
  • For each value, detect and invoke cleanup if it implements AutoCloseable, Spring’s DisposableBean, or declares a destroy()/close() method via reflection
  • Then clear the map

Locations to address:

  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/ClientFactoryImpl.java:destroy()

Suggested diff sketch:

 public void destroy() {
-    clients.clear();
+    for (Object client : clients.values()) {
+        if (client instanceof AutoCloseable) {
+            try { ((AutoCloseable) client).close(); } catch (Exception ignored) {}
+        } else {
+            // reflectively call destroy() or close() if present
+            invokeIfExists(client, "destroy");
+            invokeIfExists(client, "close");
+        }
+    }
+    clients.clear();
 }

+private void invokeIfExists(Object obj, String methodName) {
+    try {
+        Method m = obj.getClass().getMethod(methodName);
+        m.invoke(obj);
+    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {}
+}

This ensures clients with cleanup hooks are properly closed before removal.

📝 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 destroy() {
clients.clear();
}
@Override
public void destroy() {
for (Object client : clients.values()) {
if (client instanceof AutoCloseable) {
try {
((AutoCloseable) client).close();
} catch (Exception ignored) {
}
} else {
// reflectively call destroy() or close() if present
invokeIfExists(client, "destroy");
invokeIfExists(client, "close");
}
}
clients.clear();
}
private void invokeIfExists(Object obj, String methodName) {
try {
Method m = obj.getClass().getMethod(methodName);
m.invoke(obj);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
}
🤖 Prompt for AI Agents
In
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/impl/ClientFactoryImpl.java
lines 43 to 46, the destroy method only clears the clients map without invoking
any cleanup methods on the client instances. To properly release resources,
iterate over the values in the clients map, check if each client implements
AutoCloseable, DisposableBean, or has a destroy() or close() method via
reflection, invoke the appropriate cleanup method, and then clear the map. This
ensures client resources are released correctly before removal.

Copy link

codecov bot commented May 26, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.94%. Comparing base (c53e51c) to head (85b5bc2).
Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1373      +/-   ##
============================================
+ Coverage     82.91%   82.94%   +0.02%     
- Complexity     2971     2973       +2     
============================================
  Files           340      340              
  Lines          9828     9832       +4     
  Branches       1178     1178              
============================================
+ Hits           8149     8155       +6     
  Misses         1164     1164              
+ Partials        515      513       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CrazyHZM CrazyHZM merged commit e7eeb30 into sofastack:master Jun 3, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working cla:yes size/XS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant