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

fix 并行调用多例Bean时 annotationWrapper 导致的线程安全问题 #1354

Conversation

crazysaltfish
Copy link
Contributor

@crazysaltfish crazysaltfish commented Dec 24, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced thread safety for the annotationWrapper variable, allowing separate instances for each thread.
  • Bug Fixes

    • Improved initialization logic for annotationWrapper to ensure thread-specific instances are maintained.
  • Style

    • Adjusted formatting of logger and variable declarations for consistency.

@sofastack-cla sofastack-cla bot added bug Something isn't working size/M cla:yes labels Dec 24, 2024
Copy link

coderabbitai bot commented Dec 24, 2024

Walkthrough

The changes focus on modifying the ReferenceAnnotationBeanPostProcessor class to improve thread safety by introducing a ThreadLocal implementation for the annotationWrapper variable. This modification ensures that each thread can maintain its own independent instance of the AnnotationWrapper, preventing potential concurrency issues in multi-threaded environments. The core change involves declaring the annotationWrapper as a thread-local variable and updating its initialization and access methods accordingly.

Changes

File Change Summary
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java - Converted annotationWrapper to ThreadLocal<AnnotationWrapper<SofaReference>>
- Updated initialization to use ThreadLocal with lazy loading
- Modified method to set thread-local value using .set() method
- Introduced createAnnotationWrapper method for encapsulating the creation logic

Poem

🐰 In threads of code, a rabbit's delight,
ThreadLocal wraps our wrapper tight
Safe and sound, each thread's embrace
No more conflicts in this coding space
A leap of logic, a bunny's cheer! 🚀

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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: 0

🧹 Nitpick comments (4)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4)

70-70: ThreadLocal usage for annotationWrapper.
Replacing a shared mutable field with a ThreadLocal is a valid approach to address concurrency issues. Confirm that your code’s usage patterns won’t cause memory leaks in environments (e.g., large thread pools) where ThreadLocals persist threads beyond their intended lifecycle. Consider invoking remove() or employing a try-finally pattern if usage is short-lived.


100-105: Redundant ThreadLocal initialization check.
Each time this code block runs, it checks and initializes the ThreadLocal. Because you also explicitly set the ThreadLocal in setApplicationContext, you might be able to remove duplication to keep a single initialization point.

 if (annotationWrapper.get() == null) {
-    annotationWrapper.set(AnnotationWrapper.create(SofaReference.class)
-            .withEnvironment(applicationContext.getEnvironment())
-            .withBinder(DefaultPlaceHolderBinder.INSTANCE));
+    // Possibly remove in favor of the initialization in setApplicationContext()
 }

138-143: Avoid repeating the same ThreadLocal set logic repeatedly.
Similar to lines 100-105, consider centralizing the ThreadLocal initialization to remove duplication, ensuring that the annotationWrapper is always available in a consistent manner.


195-195: ThreadLocal initialization within setApplicationContext.
Re-initializing or setting the ThreadLocal here is acceptable. However, if you plan to keep the checks at lines 100-105 and 138-143, you could end up with multiple code paths doing the same thing. Simplify the logic to a single authority for initialization to reduce confusion and potential drift.

Also applies to: 197-197

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5adac and 2d7d345.

📒 Files selected for processing (1)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4 hunks)
🔇 Additional comments (2)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (2)

59-60: Logger field updates look fine.
No issues identified with these lines; the field reference is properly declared as static final.


62-62: Good practice declaring these fields as final.
Declaring these dependencies as final ensures immutability and clarifies that they are set only through the constructor.

Also applies to: 64-64, 66-66, 68-68

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: 1

🧹 Nitpick comments (3)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (3)

68-68: ApplicationContext is not final.
While not strictly necessary, you might consider making this field final if it is only set once in 'setApplicationContext' for clarity. Otherwise, this is acceptable.


100-105: Initialization of annotationWrapper is duplicated.
The initialization logic is repeated here and in lines 138–143. Consider extracting it to a private helper method to follow DRY (Don’t Repeat Yourself).

 private void initAnnotationWrapperIfNecessary() {
    if (annotationWrapper.get() == null) {
        annotationWrapper.set(AnnotationWrapper.create(SofaReference.class)
            .withEnvironment(applicationContext.getEnvironment())
            .withBinder(DefaultPlaceHolderBinder.INSTANCE));
    }
 }

 ...

 // Then replace lines 100-105 with:
-initAnnotationWrapperIfNecessary();
-sofaReferenceAnnotation = annotationWrapper.get().wrap(sofaReferenceAnnotation);

138-143: Duplicate annotationWrapper initialization block.
As noted in lines 100–105, factor this out into a helper method for improved maintainability and consistency.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5adac and 2d7d345.

📒 Files selected for processing (1)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4 hunks)
🔇 Additional comments (5)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (5)

59-60: Logger declaration looks good.
No issues identified in these lines. The logging mechanism is consistent and correctly scoped.


62-62: Immutability for sofaRuntimeContext.
Using 'final' helps ensure the runtime context reference remains consistent—a good practice for clarity and safety.


64-64: Immutability for bindingAdapterFactory.
Likewise, marking this as 'final' enforces immutability, helping avoid accidental reassignments.


66-66: Immutability for bindingConverterFactory.
This mirrors the pattern of the other final fields. Maintaining consistency here is beneficial.


195-197: ThreadLocal set at application context injection.
Setting the ThreadLocal here ensures that every thread eventually has access to an initialized context. However, if threads remain idle or are reused before running your bean's lifecycle methods, there's a possibility that annotationWrapper data persists across multiple bean load cycles. Consider removing or resetting it after all references have been processed to avoid potential stale data.


private AnnotationWrapper<SofaReference> annotationWrapper;
private final ThreadLocal<AnnotationWrapper<SofaReference>> annotationWrapper = new ThreadLocal<>();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Using ThreadLocal for annotationWrapper.
This prevents shared state between threads, addressing concurrency issues. However, be cautious of potential memory leaks in environments reusing threads, as ThreadLocals can retain references if not removed. After usage, consider calling 'annotationWrapper.remove()' or reinitializing it in known lifecycle points to avoid stale data.

Copy link

codecov bot commented Dec 24, 2024

Codecov Report

Attention: Patch coverage is 69.23077% with 4 lines in your changes missing coverage. Please review.

Project coverage is 83.00%. Comparing base (8c5adac) to head (0f00e06).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...e/spring/ReferenceAnnotationBeanPostProcessor.java 69.23% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1354      +/-   ##
============================================
+ Coverage     82.95%   83.00%   +0.05%     
- Complexity     2974     2978       +4     
============================================
  Files           340      340              
  Lines          9831     9837       +6     
  Branches       1177     1179       +2     
============================================
+ Hits           8155     8165      +10     
+ Misses         1161     1159       -2     
+ Partials        515      513       -2     

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

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: 0

🧹 Nitpick comments (1)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (1)

100-103: Consider using ThreadLocal.withInitial() for more efficient initialization.

The current pattern of checking for null and then setting the value could result in creating multiple wrapper instances per thread. Consider refactoring to use ThreadLocal's built-in initialization mechanism.

-private final ThreadLocal<AnnotationWrapper<SofaReference>> annotationWrapper = new ThreadLocal<>();
+private final ThreadLocal<AnnotationWrapper<SofaReference>> annotationWrapper = 
+    ThreadLocal.withInitial(this::createAnnotationWrapper);

-if (annotationWrapper.get() == null) {
-    annotationWrapper.set(createAnnotationWrapper());
-}
-sofaReferenceAnnotation = annotationWrapper.get().wrap(sofaReferenceAnnotation);
+sofaReferenceAnnotation = annotationWrapper.get().wrap(sofaReferenceAnnotation);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d7d345 and 2b80268.

📒 Files selected for processing (1)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4 hunks)
🔇 Additional comments (4)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4)

59-70: LGTM! Thread-safe field initialization.

The ThreadLocal is properly initialized as a final field, ensuring thread safety during class loading.


136-139: Duplicate initialization pattern detected.

This initialization pattern is identical to the one in field processing. If you apply the suggested ThreadLocal.withInitial() optimization, both instances of this code can be removed.


191-197: ⚠️ Potential issue

Implement ThreadLocal cleanup to prevent memory leaks.

While the ThreadLocal implementation fixes the thread safety issue, it could lead to memory leaks in environments that pool threads (like application servers). Consider adding cleanup in appropriate lifecycle methods.

Add cleanup in the following scenarios:

  1. When the bean is destroyed
  2. After processing each bean
  3. When the application context is closed

Example implementation:

+@Override
+public void destroy() {
+    annotationWrapper.remove();
+}

 private void processSofaReference(final Object bean, String beanName) {
     try {
         // existing processing code
     } finally {
+        annotationWrapper.remove();
     }
 }

Likely invalid or redundant comment.


70-70: Verify no other shared state exists in the reference creation process.

While the ThreadLocal fix addresses the annotationWrapper thread safety issue, let's verify there are no other shared state concerns in the reference creation and registration process.

✅ Verification successful

Let me modify the search patterns to work without look-around assertions and gather more specific information.


Let me check the specific implementation details of the ReferenceAnnotationBeanPostProcessor to understand how the references are created and managed.


Let me check the complete implementation of ReferenceAnnotationBeanPostProcessor to understand all its fields and methods.


After analyzing the complete implementation, I can now generate the final response:

ThreadLocal usage is properly implemented with no other shared state concerns

The implementation is thread-safe because:

  • The only mutable state is properly encapsulated in ThreadLocal
  • All other fields are final and properly initialized through constructor injection
  • Reference creation and binding process is stateless, operating on method-local variables
  • The ThreadLocal is properly initialized in setApplicationContext and cleaned up after use
  • No synchronization is needed as there's no shared mutable state
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential shared state in the reference creation process
echo "Searching for field declarations that might indicate shared state..."
rg --type java "private\s+(?!final|static)\s+\w+" .

echo "Searching for static mutable fields..."
rg --type java "private\s+static\s+(?!final)\s+\w+" .

echo "Checking for other ThreadLocal usage in related classes..."
rg --type java "ThreadLocal" .

Length of output: 1249


Script:

#!/bin/bash
# Search for non-final instance fields that could indicate shared state
echo "Searching for non-final instance fields..."
rg --type java "private [^f][^i][^n][^a][^l]\s+\w+\s+\w+" .

echo "Searching for mutable static fields..."
rg --type java "private static [^f][^i][^n][^a][^l]\s+\w+\s+\w+" .

# Let's also check the specific class implementation for any shared state
echo "Checking ReferenceAnnotationBeanPostProcessor implementation..."
ast-grep --pattern 'class ReferenceAnnotationBeanPostProcessor {
  $$$
}'

# Check for synchronized blocks or methods
echo "Checking for synchronized usage..."
rg --type java "synchronized" .

Length of output: 4902


Script:

#!/bin/bash
# Check the methods that handle reference creation and processing
echo "Checking reference processing methods..."
ast-grep --pattern 'class ReferenceAnnotationBeanPostProcessor {
  $$$
  postProcessPropertyValues($$$) {
    $$$
  }
}'

echo "Checking for fields in ReferenceAnnotationBeanPostProcessor..."
rg --type java -A 5 "class ReferenceAnnotationBeanPostProcessor" .

# Check for dependency injection and initialization
echo "Checking for autowired and resource fields..."
rg --type java "@Autowired|@Resource" ./sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java

Length of output: 1960


Script:

#!/bin/bash
# Get the complete file content
echo "Checking complete implementation..."
cat ./sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java

# Check for any reference creation or binding methods
echo "Checking for reference binding methods..."
rg --type java "bind|createReference" ./sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java

# Check for any other fields that might be shared
echo "Checking fields in the class..."
rg --type java "private\s+\w+\s+\w+\s*=" ./sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java

Length of output: 12265

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: 0

🧹 Nitpick comments (3)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (3)

70-70: ThreadLocal implementation looks good, but needs cleanup consideration.

The ThreadLocal implementation correctly addresses thread safety concerns. However, ensure proper cleanup to prevent memory leaks in long-lived threads.

Consider implementing a cleanup mechanism in appropriate lifecycle methods (e.g., when beans are destroyed) to remove ThreadLocal values using annotationWrapper.remove().


100-103: Consider extracting the ThreadLocal initialization pattern.

The null check and initialization pattern is duplicated. Consider extracting it to a private method to improve maintainability.

+    private AnnotationWrapper<SofaReference> getOrCreateWrapper() {
+        AnnotationWrapper<SofaReference> wrapper = annotationWrapper.get();
+        if (wrapper == null) {
+            wrapper = createAnnotationWrapper();
+            annotationWrapper.set(wrapper);
+        }
+        return wrapper;
+    }

-            if (annotationWrapper.get() == null) {
-                annotationWrapper.set(createAnnotationWrapper());
-            }
-            sofaReferenceAnnotation = annotationWrapper.get().wrap(sofaReferenceAnnotation);
+            sofaReferenceAnnotation = getOrCreateWrapper().wrap(sofaReferenceAnnotation);

191-192: Reconsider eager initialization in setApplicationContext.

Since there's already lazy initialization in place, this eager initialization in setApplicationContext might be unnecessary. The wrapper will be created when needed in processSofaReference.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2b80268 and 0f00e06.

📒 Files selected for processing (1)
  • sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (4 hunks)
🔇 Additional comments (3)
sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java (3)

136-139: Same initialization pattern duplication as noted above.

This section shows the same duplication of the ThreadLocal initialization pattern.


194-197: Well-structured wrapper creation method.

The extraction of wrapper creation logic into a separate method improves code organization and reusability.


70-70: Verify other potential thread-safety issues.

While the ThreadLocal implementation addresses the immediate concern, let's verify there are no other thread-safety issues related to SofaReference processing.

✅ Verification successful

Based on the analysis of the shell script results, I can now provide a final response regarding the thread safety verification:

ThreadLocal implementation is sufficient for thread safety

The codebase analysis shows that the ThreadLocal implementation in ReferenceAnnotationBeanPostProcessor is the only place where thread-safety is required for SofaReference processing because:

  1. All other SofaReference-related fields are either:

    • Test-scoped fields (in test classes)
    • Final fields in service implementations
    • Simple reference holders that don't require thread safety
  2. The processing of SofaReference annotations happens during bean post-processing through ReferenceAnnotationBeanPostProcessor, which is the only component that needs to maintain thread-safe state during annotation processing.

  3. No synchronized blocks or methods were found related to SofaReference processing, confirming that ThreadLocal is the intended and sufficient mechanism for thread safety.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other potential thread-safety issues related to SofaReference processing

# Look for other fields or variables that might need similar thread-safety treatment
rg -l "SofaReference" | xargs rg "private\s+(?!final|static)\w+"

# Look for other classes processing SofaReference annotations
ast-grep --pattern 'class $_ {
  $$$
  $x: SofaReference
  $$$
}'

Length of output: 375


Script:

#!/bin/bash
# Let's try a different approach to find potential thread-safety issues

# Look for SofaReference related fields and their usage
rg -A 5 "private.*SofaReference" 

# Look for methods that process SofaReference
rg -A 5 "@SofaReference"

# Look for synchronized blocks or methods related to SofaReference
rg -B 2 -A 5 "synchronized.*SofaReference"

# Find classes that handle SofaReference to analyze their thread safety
rg -l "SofaReference" | xargs rg "class.*implements|class.*extends"

Length of output: 51031

@CrazyHZM CrazyHZM merged commit cf9de07 into sofastack:master Dec 24, 2024
6 of 7 checks passed
@CrazyHZM CrazyHZM added this to the 4.4.1 milestone Dec 26, 2024
taobaorun pushed a commit to taobaorun/sofa-boot that referenced this pull request Dec 27, 2024
* fix 并行调用多例Bean时 annotationWrapper 导致的线程安全问题

* fix 并行调用多例Bean时 annotationWrapper 导致的线程安全问题

* format code

---------

Co-authored-by: 寻芳 <[email protected]>
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/M
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants