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

Module deployments are deduplicated when static merge deployments(#338) #1035

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Willhow-Gao
Copy link

@Willhow-Gao Willhow-Gao commented Nov 23, 2024

static merge deployments optimize issue 338

Summary by CodeRabbit

  • New Features

    • Introduced a conditional registration method for business entities, preventing duplicate registrations.
    • Added a new interface method for registering business instances only if they are not already present.
  • Bug Fixes

    • Enhanced registration logic to improve integrity and prevent duplicates.
  • Tests

    • Updated tests to reflect changes in the registration logic and improved mocking strategies.

Copy link

sofastack-cla bot commented Nov 23, 2024

Hi @Willhow-Gao, welcome to SOFAStack community, Please sign Contributor License Agreement!

After you signed CLA, we will automatically sync the status of this pull request in 3 minutes.

Copy link
Contributor

coderabbitai bot commented Nov 23, 2024

Walkthrough

The pull request introduces modifications to the business entity registration logic within the ArkContainer, HandleArchiveStage, and BizManagerServiceImpl classes. The addStaticBiz and processStaticBizFromClasspath methods now utilize a new method, registerBizIfAbsent, which registers a business entity only if it is not already present. This change enhances the integrity of the registration process by preventing duplicate entries. Additionally, the HandleArchiveStageTest class has been updated to reflect these changes in its testing strategy.

Changes

File Change Summary
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java Modified addStaticBiz to call registerBizIfAbsent instead of registerBiz.
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java Updated processStaticBizFromClasspath to use registerBizIfAbsent instead of registerBiz.
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java Added registerBizIfAbsent(Biz biz) method for conditional registration of business entities.
sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java Enhanced tests to mock ArkClient and check for registerBizIfAbsent invocation in testProcessStaticBizFromClasspath.
sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java Added registerBizIfAbsent(Biz biz) method to the BizManagerService interface.

Possibly related PRs

  • add stop biz state #1026: The changes in this PR involve modifying the registration logic for business entities, specifically introducing a new state STOPPED in the BizModel and updating the expected state in tests, which aligns with the changes made in the main PR regarding conditional registration of business entities to prevent duplicates.

Suggested labels

size/S

Suggested reviewers

  • gaosaroma
  • yuanyuancin

Poem

🐇 In the land of code where biz entities play,
We check for duplicates, keeping chaos at bay.
With registerIfAbsent, we’re clever and spry,
A hop and a skip, no more biz to deny!
So let’s celebrate changes, both big and small,
For a tidy registration, we’ll stand proud and tall! 🎉


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

🧹 Outside diff range and nitpick comments (6)
sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java (2)

160-161: Add Javadoc documentation for the new method.

The new registerBizIfAbsent method lacks Javadoc documentation. Please add documentation following the interface's convention to describe:

  • The method's purpose
  • The biz parameter
  • The return value semantics (when it returns true/false)

Consider this template:

+    /**
+     * Register Biz only if it's not already registered
+     *
+     * @param biz The business module to register
+     * @return true if the biz was registered, false if it already exists
+     */
     boolean registerBizIfAbsent(Biz biz);

160-161: Consider reorganizing method placement.

For better code organization and readability, consider moving this method next to the similar registerBiz method (around line 42), as they are functionally related.

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1)

226-239: Document the thread-safety guarantees.

The method implements thread-safe registration, but this behavior isn't documented. Consider adding Javadoc to clarify the concurrency guarantees.

Add documentation like this:

+    /**
+     * Thread-safe registration of a Biz instance if not already present.
+     * This method ensures that only one instance of a Biz with the same name and version
+     * can be registered, even under concurrent access.
+     *
+     * @param biz the Biz instance to register
+     * @return true if the Biz was registered, false if an instance was already present
+     * @throws IllegalArgumentException if biz is null or not in RESOLVED state
+     */
     @Override
     public boolean registerBizIfAbsent(Biz biz) {
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (2)

197-201: Consider adding logging for skipped registrations.

To improve observability, consider adding debug-level logging when a biz registration is skipped due to it already being present.

 public void processStaticBizFromClasspath(PipelineContext pipelineContext) throws Exception {
     ExecutableArchive executableArchive = pipelineContext.getExecutableArchive();
     List<BizArchive> bizArchives = executableArchive.getBizArchives();
     for (BizArchive bizArchive : bizArchives) {
         Biz biz = bizFactoryService.createBiz(bizArchive);
-        bizManagerService.registerBizIfAbsent(biz);
+        if (!bizManagerService.registerBizIfAbsent(biz)) {
+            ArkLoggerFactory.getDefaultLogger().debug(
+                "Skipped registering duplicate biz: {}", biz.getIdentity());
+        }
     }
 }

197-201: Consider adding unit tests for deduplication.

To ensure the deduplication behavior works as expected, consider adding unit tests that verify:

  1. First registration succeeds
  2. Duplicate registration is properly handled
  3. Error cases are properly handled

Would you like me to help generate the unit test cases?

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (1)

Line range hint 196-202: Consider enhancing error reporting for duplicate biz detection

Since we're now explicitly handling duplicate biz scenarios, consider adding debug/info level logging when a duplicate is detected. This would help with troubleshooting deployment issues.

    for (BizArchive bizArchive : bizArchives) {
        Biz biz = ArkClient.getBizFactoryService().createBiz(bizArchive);
-       ArkClient.getBizManagerService().registerBizIfAbsent(biz);
+       if (!ArkClient.getBizManagerService().registerBizIfAbsent(biz)) {
+           ArkLoggerFactory.getDefaultLogger().info(
+               "Skipping duplicate biz registration: " + biz.getIdentity());
+       }
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab39b4 and 6a936d2.

📒 Files selected for processing (5)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java (3 hunks)
  • sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java (1 hunks)
🔇 Additional comments (5)
sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java (1)

160-161: LGTM on the method signature.

The method signature aligns well with the PR's objective of deduplicating module deployments and follows the interface's existing patterns.

Let's verify the implementation of this method:

✅ Verification successful

Let me gather more information about the implementation.


Implementation is thread-safe and correctly handles deduplication

The implementation is well-designed with proper thread safety using a combination of ConcurrentHashMap and synchronized block:

  • Uses ConcurrentHashMap for the main bizRegistration storage
  • Implements double-checked locking pattern for thread-safe registration
  • Correctly uses putIfAbsent for the outer map and synchronized block for the inner map operations
  • Properly validates input parameters before registration
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation to ensure thread-safety and proper deduplication logic

# Look for the implementation class
rg -l "class.*BizManagerServiceImpl.*implements.*BizManagerService"

# Check the implementation of registerBizIfAbsent
ast-grep --pattern 'boolean registerBizIfAbsent(Biz $_) {
  $$$
}'

Length of output: 257


Script:

#!/bin/bash
# Get the implementation details from BizManagerServiceImpl.java
rg -A 10 "boolean registerBizIfAbsent\(Biz" sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java

# Check if there's any synchronization mechanism in the class
rg "synchronized|Lock|Concurrent" sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java

Length of output: 2258

sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java (1)

Line range hint 19-46: LGTM! Imports are appropriate for the new test implementation.

The new imports (ArkClient, Biz, MockedStatic) are necessary to support the static mocking strategy used in the updated test.

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (1)

200-200: LGTM! Good improvement for deduplication.

The change from registerBiz to registerBizIfAbsent aligns well with the PR objective of deduplicating module deployments during static merge. This prevents potential issues with duplicate registrations while maintaining the existing functionality.

Let's verify the error handling in the new registration path:

✅ Verification successful

Error handling is properly implemented in registerBizIfAbsent

The implementation of registerBizIfAbsent in BizManagerServiceImpl has appropriate error handling:

  • Input validation using AssertUtils to check for null and valid state
  • Thread-safe implementation using synchronization
  • Proper state verification before registration
  • Returns boolean to indicate success/failure

The change from registerBiz to registerBizIfAbsent is safe and well-implemented.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling patterns in registerBizIfAbsent implementation
ast-grep --pattern 'registerBizIfAbsent($$$) {
  $$$
  throw $$$
  $$$
}'

# Check for any existing error handling tests
rg -l "registerBizIfAbsent.*Exception" -t java

Length of output: 124


Script:

#!/bin/bash
# Let's first find the implementation of registerBizIfAbsent
ast-grep --pattern 'registerBizIfAbsent($$$) {
  $$$
}'

# Also search for any test files related to this functionality
rg -l "registerBizIfAbsent" -t java

# Look for any exception handling patterns in the service
rg "throws|catch|Exception" -A 3 -B 3 --type java -g "*BizManagerService*"

Length of output: 4429


Script:

#!/bin/bash
# Let's look at the actual implementation of registerBizIfAbsent in BizManagerServiceImpl
rg -A 10 "registerBizIfAbsent" sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java

# Also check the test coverage for this specific method
rg -A 10 "registerBizIfAbsent" sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java

Length of output: 1390

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (2)

200-200: LGTM! Verify error handling in BizManagerService

The change from registerBiz to registerBizIfAbsent aligns with the PR objective of deduplicating module deployments. This is a good improvement to prevent duplicate registrations during static merge deployments.

Let's verify the error handling in the new method:

✅ Verification successful

LGTM! Error handling is properly implemented

The implementation of registerBizIfAbsent in BizManagerServiceImpl has robust error handling:

  • Validates non-null biz parameter using AssertUtils.assertNotNull
  • Verifies biz state using AssertUtils.isTrue
  • Uses thread-safe concurrent collections and proper synchronization
  • Implements proper duplicate checking with double-checked locking pattern

The change is safe and well-implemented across the codebase:

  • Used consistently in both ArkContainer and HandleArchiveStage
  • Maintains thread safety with ConcurrentHashMap
  • Returns boolean to indicate registration status
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check error handling in BizManagerService implementation
# Look for error handling patterns in registerBizIfAbsent implementation

# Search for the implementation
ast-grep --pattern 'class BizManagerServiceImpl {
  $$$
  registerBizIfAbsent($$$) {
    $$$
  }
  $$$
}'

# Search for exception handling
rg -A 5 "registerBizIfAbsent.*throws"

Length of output: 147


Script:

#!/bin/bash
# Let's try a broader search to locate the implementation and error handling

# Find all files containing BizManagerService
fd -e java BizManagerService

# Search for registerBizIfAbsent implementation with context
rg -A 10 "registerBizIfAbsent" 

# Search for BizManagerServiceImpl class with context
rg -A 20 "class BizManagerServiceImpl"

# Look for any exception handling related to biz registration
rg -A 5 "register.*Biz.*throw"

Length of output: 11417


Line range hint 196-202: Verify consistent registration behavior across all entry points

To ensure system-wide consistency, we should verify that all biz registration entry points use the same deduplication strategy.

✅ Verification successful

Let me analyze the registration behavior across the codebase based on the search results.


Registration behavior is consistent across entry points

After analyzing the implementation and usage patterns, I can confirm that the registration behavior is consistent across all entry points:

  • registerBiz is used in test code and internal pipeline stages for direct registration
  • registerBizIfAbsent is used in production code paths (ArkContainer, HandleArchiveStage) for safe concurrent registration
  • The registerBizIfAbsent implementation properly handles thread safety with double-checked locking pattern when adding new biz entries

The new code in ArkContainer correctly uses registerBizIfAbsent, which is the appropriate method for production registration paths.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all biz registration calls to ensure consistent behavior

# Search for direct registration calls
rg "registerBiz\(" 

# Search for the new method usage
rg "registerBizIfAbsent\("

# Look for other potential registration points
ast-grep --pattern 'BizManagerService.$$$register$$$'

Length of output: 9196


Script:

#!/bin/bash
# Let's examine the implementation of both registration methods
ast-grep --pattern 'public boolean registerBiz(Biz biz) {
  $$$
}'

ast-grep --pattern 'public boolean registerBizIfAbsent(Biz biz) {
  $$$
}'

# Also check where these methods are primarily used
rg -B 2 -A 2 "registerBiz\(" sofa-ark-parent/core-impl/container/src/main/java/
rg -B 2 -A 2 "registerBizIfAbsent\(" sofa-ark-parent/core-impl/container/src/main/java/

Length of output: 9978


Script:

#!/bin/bash
# Let's examine the actual implementation of BizManagerServiceImpl to understand the registration logic
cat sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java

Length of output: 9957

Comment on lines +171 to +173
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
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

Fix incorrect test data: Biz object is null

The test is verifying registerBizIfAbsent(null), which doesn't properly test the deduplication logic. The Biz object should be created from the mocked BizArchive.

Apply this diff to fix the test:

-            handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
-            verify(bizFactoryService, times(1)).createBiz((bizArchive));
-            verify(bizManagerService, times(1)).registerBizIfAbsent(null);
+            Biz mockBiz = mock(Biz.class);
+            when(bizFactoryService.createBiz(bizArchive)).thenReturn(mockBiz);
+            
+            handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
+            
+            verify(bizFactoryService, times(1)).createBiz(bizArchive);
+            verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);
📝 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
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
Biz mockBiz = mock(Biz.class);
when(bizFactoryService.createBiz(bizArchive)).thenReturn(mockBiz);
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz(bizArchive);
verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);

Comment on lines +158 to +176
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));

handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
}finally {
arkClientMockedStatic.close();
}
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

Enhance test coverage for deduplication scenarios

The test should verify the actual deduplication behavior by testing scenarios where:

  1. A Biz doesn't exist (should register)
  2. A Biz already exists (should skip registration)

Consider adding these test cases:

+    @Test
+    public void testProcessStaticBizFromClasspathWithExistingBiz() throws Exception {
+        MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
+        arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
+        try {
+            // Setup existing biz
+            ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
+            ConcurrentHashMap<String, Biz> versionMap = new ConcurrentHashMap<>();
+            Biz existingBiz = mock(Biz.class);
+            when(existingBiz.getBizName()).thenReturn("biz1");
+            when(existingBiz.getBizVersion()).thenReturn("0.0.1");
+            versionMap.put("0.0.1", existingBiz);
+            map.put("biz1", versionMap);
+            when(bizManagerService.getBizRegistration()).thenReturn(map);
+
+            // Setup new biz with same name and version
+            BizArchive bizArchive = mock(BizArchive.class);
+            Manifest manifest = new Manifest();
+            Attributes mainAttributes = manifest.getMainAttributes();
+            mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
+            mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
+            when(bizArchive.getManifest()).thenReturn(manifest);
+            when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
+
+            Biz newBiz = mock(Biz.class);
+            when(bizFactoryService.createBiz(bizArchive)).thenReturn(newBiz);
+
+            handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
+
+            // Verify biz was created but not registered due to existing registration
+            verify(bizFactoryService, times(1)).createBiz(bizArchive);
+            verify(bizManagerService, times(1)).registerBizIfAbsent(newBiz);
+            verify(bizManagerService, never()).registerBiz(any());
+        } finally {
+            arkClientMockedStatic.close();
+        }
+    }
📝 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
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
}finally {
arkClientMockedStatic.close();
}
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
}finally {
arkClientMockedStatic.close();
}
@Test
public void testProcessStaticBizFromClasspathWithExistingBiz() throws Exception {
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
// Setup existing biz
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Biz> versionMap = new ConcurrentHashMap<>();
Biz existingBiz = mock(Biz.class);
when(existingBiz.getBizName()).thenReturn("biz1");
when(existingBiz.getBizVersion()).thenReturn("0.0.1");
versionMap.put("0.0.1", existingBiz);
map.put("biz1", versionMap);
when(bizManagerService.getBizRegistration()).thenReturn(map);
// Setup new biz with same name and version
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
Biz newBiz = mock(Biz.class);
when(bizFactoryService.createBiz(bizArchive)).thenReturn(newBiz);
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
// Verify biz was created but not registered due to existing registration
verify(bizFactoryService, times(1)).createBiz(bizArchive);
verify(bizManagerService, times(1)).registerBizIfAbsent(newBiz);
verify(bizManagerService, never()).registerBiz(any());
} finally {
arkClientMockedStatic.close();
}
}

Comment on lines 226 to 239
public boolean registerBizIfAbsent(Biz biz) {
AssertUtils.assertNotNull(biz, "Biz must not be null.");
AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
synchronized (bizRegistration.get(biz.getBizName())) {
if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
return bizCache.put(biz.getBizVersion(), biz) == null;
}
}
}
return false;
}
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

Thread safety concern in double-checked locking implementation.

The synchronized block locks on a ConcurrentHashMap value which could potentially be replaced by another thread, leading to synchronization issues. Consider using a more robust synchronization approach.

Here's a suggested implementation that addresses this concern:

     @Override
     public boolean registerBizIfAbsent(Biz biz) {
         AssertUtils.assertNotNull(biz, "Biz must not be null.");
         AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
-        if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
-            bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
-            synchronized (bizRegistration.get(biz.getBizName())) {
-                if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
-                    ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
-                    return bizCache.put(biz.getBizVersion(), biz) == null;
-                }
-            }
+        String bizName = biz.getBizName();
+        String bizVersion = biz.getBizVersion();
+        
+        // Create the version map if absent
+        bizRegistration.putIfAbsent(bizName, new ConcurrentHashMap<>(16));
+        ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(bizName);
+        
+        // Use putIfAbsent for atomic check-then-act
+        return bizCache.putIfAbsent(bizVersion, biz) == null;
         }
-        return false;
     }

The suggested implementation:

  1. Eliminates the need for explicit synchronization by leveraging ConcurrentHashMap's atomic operations
  2. Reduces redundant getBiz calls
  3. Maintains thread safety while improving performance
📝 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 boolean registerBizIfAbsent(Biz biz) {
AssertUtils.assertNotNull(biz, "Biz must not be null.");
AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
synchronized (bizRegistration.get(biz.getBizName())) {
if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
return bizCache.put(biz.getBizVersion(), biz) == null;
}
}
}
return false;
}
public boolean registerBizIfAbsent(Biz biz) {
AssertUtils.assertNotNull(biz, "Biz must not be null.");
AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
String bizName = biz.getBizName();
String bizVersion = biz.getBizVersion();
// Create the version map if absent
bizRegistration.putIfAbsent(bizName, new ConcurrentHashMap<>(16));
ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(bizName);
// Use putIfAbsent for atomic check-then-act
return bizCache.putIfAbsent(bizVersion, biz) == null;
}

💡 Codebase verification

Add concurrent test cases for registerBizIfAbsent method

The current test suite (BizManagerServiceTest.java) lacks concurrent testing for the registerBizIfAbsent method, while the codebase already has examples of concurrent testing patterns in ClassLoaderConcurrencyTest.java. Consider adding test cases that:

  • Use multiple threads to register the same biz simultaneously
  • Verify that only one registration succeeds
  • Follow the concurrent testing pattern from ClassLoaderConcurrencyTest.java using CountDownLatch and ThreadPoolExecutor
🔗 Analysis chain

Consider adding unit tests for concurrent scenarios.

The new method handles concurrent registration attempts, but there are no visible tests verifying this behavior.

Let's check for existing concurrent tests:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for test files related to BizManagerServiceImpl
fd -e java -e groovy "BizManagerServiceImpl.*Test" 

# Search for concurrent testing patterns
rg -i "concurrent|thread|async" -A 5 -B 5 "BizManagerServiceImpl.*Test"

Length of output: 194


Script:

#!/bin/bash
# Let's try a broader search for test files related to BizManager
fd -e java -e groovy "BizManager.*Test"

# Search for any test files in the same directory as BizManagerServiceImpl
fd -e java -e groovy ".*Test" sofa-ark-parent/core-impl/container/src/test/

# Let's also check if there are any test files that might test concurrent behavior
rg -i "concurrent.*test|thread.*test" sofa-ark-parent/core-impl/container/src/test/

Length of output: 5464


Script:

#!/bin/bash
# Let's examine the content of BizManagerServiceTest.java to see if it has any concurrent testing
cat sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceTest.java

# Also check ClassLoaderConcurrencyTest.java as it might have patterns we can follow
cat sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/service/classloader/ClassLoaderConcurrencyTest.java

Length of output: 10909

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

🧹 Outside diff range and nitpick comments (5)
sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java (2)

160-161: Add JavaDoc documentation for the new method.

For consistency with other methods in the interface, please add JavaDoc documentation that describes:

  • The method's purpose
  • The parameter description
  • The return value meaning (true if registered, false if already exists)
+    /**
+     * Register Biz only if it's not already registered
+     *
+     * @param biz The business entity to register
+     * @return true if the biz was registered, false if it already exists
+     */
     boolean registerBizIfAbsent(Biz biz);

160-161: LGTM! The new method enhances deduplication.

The addition of registerBizIfAbsent is a good enhancement that supports the PR's objective of deduplicating module deployments during static merge deployments. It provides an atomic way to check-and-register business entities, preventing duplicate registrations.

Consider implementing this method in a thread-safe manner in the implementation class, possibly using the putIfAbsent operation of the ConcurrentHashMap that's already used in getBizRegistration().

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1)

225-239: LGTM! Thread-safe implementation for deduplication.

The implementation correctly handles concurrent registration attempts using double-checked locking pattern and maintains consistency with existing validation logic.

Consider optimizing the synchronization granularity by using a more specific lock object:

-            synchronized (bizRegistration.get(biz.getBizName())) {
+            ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
+            synchronized (bizCache) {
                 if (getBiz(biz.getBizName(), biz.getBizVersion()) == null) {
-                    ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
                     return bizCache.put(biz.getBizVersion(), biz) == null;
                 }
             }

This change:

  1. Reduces the number of map lookups
  2. Makes the synchronized block's scope more explicit
  3. Maintains the same thread-safety guarantees
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (1)

199-201: Consider adding debug logging for skipped registrations.

To aid in troubleshooting, consider logging when a biz registration is skipped due to an existing registration.

 public void processStaticBizFromClasspath(PipelineContext pipelineContext) throws Exception {
     ExecutableArchive executableArchive = pipelineContext.getExecutableArchive();
     List<BizArchive> bizArchives = executableArchive.getBizArchives();
     for (BizArchive bizArchive : bizArchives) {
         Biz biz = bizFactoryService.createBiz(bizArchive);
-        bizManagerService.registerBizIfAbsent(biz);
+        if (!bizManagerService.registerBizIfAbsent(biz)) {
+            ArkLoggerFactory.getDefaultLogger().debug(
+                "Skipped registering duplicate biz: {}", biz.getIdentity());
+        }
     }
 }
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (1)

Line range hint 156-173: Consider documenting the deployment order guarantees

The deployment lifecycle involves multiple stages (master biz → static biz → custom hooks), and the new deduplication logic makes the order of deployment more significant. Consider documenting:

  1. The order guarantees between different deployment stages
  2. How version conflicts are resolved when the same biz is registered through different paths

Would you like me to help draft the documentation for the deployment lifecycle?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab39b4 and 6a936d2.

📒 Files selected for processing (5)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1 hunks)
  • sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java (3 hunks)
  • sofa-ark-parent/core/spi/src/main/java/com/alipay/sofa/ark/spi/service/biz/BizManagerService.java (1 hunks)
🔇 Additional comments (4)
sofa-ark-parent/core-impl/container/src/test/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStageTest.java (1)

Line range hint 19-46: LGTM! Import changes align with new test requirements.

The new imports support static method mocking and concurrent data structures needed for testing the deduplication logic.

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1)

225-239: Verify integration with dependent components.

Let's confirm that all callers properly handle the return value of registerBizIfAbsent.

✅ Verification successful

Let me gather more context about error handling in the calling methods.


Integration with dependent components is properly implemented

The verification shows that registerBizIfAbsent is used correctly in all calling contexts:

  • In ArkContainer.addStaticBiz, the method is called within a proper error handling context (wrapped in a method that declares throws IOException)
  • In HandleArchiveStage.processStaticBizFromClasspath, it's called within a method that declares throws Exception
  • The method's preconditions are enforced through AssertUtils checks
  • The return value (boolean) is appropriately used as a status indicator, not requiring explicit handling since a false return simply means the biz was already registered
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all callers of registerBizIfAbsent and verify their error handling

# Search for direct method calls
echo "Searching for direct callers of registerBizIfAbsent..."
rg -A 5 "registerBizIfAbsent\(" 

# Search for potential error handling patterns around the calls
echo "Checking error handling patterns..."
ast-grep --pattern 'if ($service.registerBizIfAbsent($_)) {
  $$$
}'

Length of output: 4234


Script:

#!/bin/bash
# Check the implementation of methods that call registerBizIfAbsent

# Check ArkContainer.addStaticBiz implementation
echo "Checking ArkContainer.addStaticBiz implementation:"
rg -B 5 -A 10 "addStaticBiz" sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java

# Check HandleArchiveStage.processStaticBizFromClasspath implementation
echo -e "\nChecking HandleArchiveStage.processStaticBizFromClasspath implementation:"
rg -B 5 -A 10 "processStaticBizFromClasspath" sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java

# Check if there's any exception handling around these calls
echo -e "\nChecking for try-catch blocks around registerBizIfAbsent calls:"
ast-grep --pattern 'try {
  $$$
  registerBizIfAbsent($$$)
  $$$
} catch ($$$) {
  $$$
}'

Length of output: 2806

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/pipeline/HandleArchiveStage.java (1)

200-200: LGTM! The change aligns with deduplication objectives.

The switch to registerBizIfAbsent effectively prevents duplicate module registrations during static merge deployments, which is the core objective of this PR.

Let's verify the thread safety and implementation of registerBizIfAbsent:

✅ Verification successful

Let me search for the implementation of registerBizIfAbsent to verify thread safety.


Let me check the implementation of registerBizIfAbsent in BizManagerServiceImpl.java to verify thread safety.


Thread-safe implementation confirmed, change is safe

The registerBizIfAbsent implementation in BizManagerServiceImpl is thread-safe:

  • Uses ConcurrentHashMap for both levels of caching
  • Implements proper double-checked locking pattern with synchronized block
  • Maintains consistent state checks and validations

The change from registerBiz to registerBizIfAbsent is a safe improvement that:

  • Prevents duplicate registrations
  • Maintains thread safety
  • Preserves first-registered instance
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the implementation of registerBizIfAbsent
ast-grep --pattern 'registerBizIfAbsent($$$) {
  $$$
}'

# Check for other registration points to ensure consistent behavior
rg -A 5 'registerBiz\(' --type java

Length of output: 42474


Script:

#!/bin/bash
# Search for the implementation of registerBizIfAbsent
ast-grep --pattern 'registerBizIfAbsent($$$) {
  $$$
}'

# Also search for any file containing this method declaration
rg 'registerBizIfAbsent' --type java

Length of output: 933


Script:

#!/bin/bash
# Get the implementation of registerBizIfAbsent
cat sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java

Length of output: 9957

sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/ArkContainer.java (1)

200-200: LGTM! Verify thread safety and version handling

The change from registerBiz to registerBizIfAbsent aligns with the PR objective of deduplicating module deployments. This prevents duplicate registration while maintaining first-registered-wins semantics.

Let's verify the implementation of registerBizIfAbsent and its thread safety:

✅ Verification successful

Based on the shell script results, I can now verify the thread safety and version handling in the registerBizIfAbsent implementation:

  1. The implementation in BizManagerServiceImpl uses proper synchronization:

    • Uses ConcurrentHashMap for the bizRegistration map
    • Has synchronized block on the biz name's map for atomic check-then-act
    • Performs version check via getBiz before registration
  2. The version handling is robust:

    • Checks for existing biz with same name and version before registration
    • Uses two-level concurrent map structure for name->version->biz mapping
    • Maintains proper state validation (RESOLVED state check)
  3. The change is used consistently:

    • Only used in static biz deployment scenarios
    • Other registration paths still use registerBiz for strict control

The implementation is thread-safe and handles versioning correctly

The change from registerBiz to registerBizIfAbsent is properly implemented with thread-safe concurrent collections and synchronized blocks. The version handling ensures no duplicate registrations of the same biz version.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of registerBizIfAbsent and related synchronization

# Check BizManagerService implementation for thread safety
ast-grep --pattern 'class BizManagerServiceImpl {
  $$$
  registerBizIfAbsent($$$) {
    $$$
  }
  $$$
}'

# Check for potential race conditions in concurrent registrations
rg -A 5 'registerBizIfAbsent|registerBiz' --type java

# Check version handling in duplicate scenarios
ast-grep --pattern 'class BizManagerServiceImpl {
  $$$
  getBiz($$$) {
    $$$
  }
  $$$
}'

Length of output: 51307

Comment on lines +158 to +176
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));

handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
}finally {
arkClientMockedStatic.close();
}
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

Test implementation needs improvement

Several issues need to be addressed:

  1. The verification registerBizIfAbsent(null) on line 173 appears incorrect as we're creating a biz archive and factory but not verifying the actual biz object.
  2. The test doesn't verify the deduplication behavior which is the main objective of this PR.
  3. Missing verification of manifest attributes usage.

Consider refactoring the test like this:

 @Test
 public void testProcessStaticBizFromClasspath() throws Exception {
     MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
     arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
     try {
         BizArchive bizArchive = mock(BizArchive.class);
         Manifest manifest = new Manifest();
         Attributes mainAttributes = manifest.getMainAttributes();
         mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
         mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
         when(bizArchive.getManifest()).thenReturn(manifest);
         ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
         when(bizManagerService.getBizRegistration()).thenReturn(map);
         when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
+        // Create and configure mock Biz
+        Biz mockBiz = mock(Biz.class);
+        when(mockBiz.getBizName()).thenReturn("biz1");
+        when(mockBiz.getBizVersion()).thenReturn("0.0.1");
+        when(bizFactoryService.createBiz(bizArchive)).thenReturn(mockBiz);
 
         handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
-        verify(bizFactoryService, times(1)).createBiz((bizArchive));
-        verify(bizManagerService, times(1)).registerBizIfAbsent(null);
+        verify(bizFactoryService, times(1)).createBiz(bizArchive);
+        verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);
+        
+        // Test deduplication
+        handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
+        // Should still be called only once total
+        verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);
     }finally {
         arkClientMockedStatic.close();
     }
 }

This refactored version:

  1. Properly creates and verifies a mock Biz object
  2. Tests the deduplication behavior by calling the method twice
  3. Verifies that registration happens only once
📝 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
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz((bizArchive));
verify(bizManagerService, times(1)).registerBizIfAbsent(null);
}finally {
arkClientMockedStatic.close();
}
MockedStatic<ArkClient> arkClientMockedStatic = mockStatic(ArkClient.class);
arkClientMockedStatic.when(ArkClient::getBizManagerService).thenReturn(bizManagerService);
try {
BizArchive bizArchive = mock(BizArchive.class);
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue(ARK_BIZ_NAME, "biz1");
mainAttributes.putValue(ARK_BIZ_VERSION, "0.0.1");
when(bizArchive.getManifest()).thenReturn(manifest);
ConcurrentHashMap<String, ConcurrentHashMap<String, Biz>> map = new ConcurrentHashMap<>();
when(bizManagerService.getBizRegistration()).thenReturn(map);
when(executableArchive.getBizArchives()).thenReturn(asList(bizArchive));
// Create and configure mock Biz
Biz mockBiz = mock(Biz.class);
when(mockBiz.getBizName()).thenReturn("biz1");
when(mockBiz.getBizVersion()).thenReturn("0.0.1");
when(bizFactoryService.createBiz(bizArchive)).thenReturn(mockBiz);
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
verify(bizFactoryService, times(1)).createBiz(bizArchive);
verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);
// Test deduplication
handleArchiveStage.processStaticBizFromClasspath(pipelineContext);
// Should still be called only once total
verify(bizManagerService, times(1)).registerBizIfAbsent(mockBiz);
}finally {
arkClientMockedStatic.close();
}

Copy link

codecov bot commented Nov 24, 2024

Codecov Report

Attention: Patch coverage is 50.00000% with 6 lines in your changes missing coverage. Please review.

Project coverage is 78.29%. Comparing base (7ab39b4) to head (6a936d2).

Files with missing lines Patch % Lines
...k/container/service/biz/BizManagerServiceImpl.java 40.00% 2 Missing and 4 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1035      +/-   ##
============================================
+ Coverage     78.20%   78.29%   +0.08%     
- Complexity      874      875       +1     
============================================
  Files           171      171              
  Lines          7056     7066      +10     
  Branches       1037     1041       +4     
============================================
+ Hits           5518     5532      +14     
+ Misses         1006      997       -9     
- Partials        532      537       +5     

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


🚨 Try these New Features:

@sofastack-cla sofastack-cla bot added cla:yes and removed cla:no labels Nov 25, 2024
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: 0

🧹 Outside diff range and nitpick comments (2)
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (2)

226-232: Consider reducing code duplication with registerBiz.

The validation and map initialization logic is duplicated between registerBiz and registerBizIfAbsent. Consider extracting common code into a private helper method.

+    private void validateAndInitializeBiz(Biz biz) {
+        AssertUtils.assertNotNull(biz, "Biz must not be null.");
+        AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
+        bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
+    }
+
     @Override
     public boolean registerBiz(Biz biz) {
-        AssertUtils.assertNotNull(biz, "Biz must not be null.");
-        AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
-        bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
+        validateAndInitializeBiz(biz);
         ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
         return bizCache.put(biz.getBizVersion(), biz) == null;
     }

     @Override
     public boolean registerBizIfAbsent(Biz biz) {
-        AssertUtils.assertNotNull(biz, "Biz must not be null.");
-        AssertUtils.isTrue(biz.getBizState() == BizState.RESOLVED, "BizState must be RESOLVED.");
-        bizRegistration.putIfAbsent(biz.getBizName(), new ConcurrentHashMap<>(16));
+        validateAndInitializeBiz(biz);
         ConcurrentHashMap<String, Biz> bizCache = bizRegistration.get(biz.getBizName());
         return bizCache.putIfAbsent(biz.getBizVersion(), biz) == null;
     }

226-232: Add Javadoc to document the method's behavior.

The method would benefit from documentation explaining:

  • Its purpose in deduplicating module deployments
  • The return value semantics
  • The state requirements for the input Biz
+    /**
+     * Registers a Biz only if it hasn't been registered before.
+     * This method is used to deduplicate module deployments in static merge scenarios.
+     *
+     * @param biz the Biz to register, must be in RESOLVED state
+     * @return true if the Biz was registered, false if it was already present
+     * @throws IllegalArgumentException if biz is null or not in RESOLVED state
+     */
     @Override
     public boolean registerBizIfAbsent(Biz biz) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6a936d2 and a3a8eb7.

📒 Files selected for processing (1)
  • sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1 hunks)
🔇 Additional comments (1)
sofa-ark-parent/core-impl/container/src/main/java/com/alipay/sofa/ark/container/service/biz/BizManagerServiceImpl.java (1)

226-232: LGTM! Thread-safe implementation for conditional registration.

The implementation correctly uses ConcurrentHashMap's atomic operations to ensure thread safety without explicit synchronization. The use of putIfAbsent instead of put properly handles the deduplication requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant