[ISSUE #15470] Upgrade SOFA-JRaft to 1.4.1 - #15530
Conversation
|
Thanks for your this PR. 🙏 感谢您提交的PR。 🙏 |
Request changes: preserve bounded callback behavior without silently losing ACK futuresReplacing the transitive There is a race between context initialization, future registration, and trimming:
This is misleading during incident investigation because the client may have returned the ACK normally, while the server discarded the matching context because of capacity pressure. Please consider one of the following two options. Option 1 — Keep the JDK-only replacement and make trimming concurrency-safeThis is the preferred option because it keeps the change local to 1. Preserve the public field type
Please keep the public declaration and back it with a private concurrent store: private static final ConcurrentMap<String, Map<String, DefaultRequestFuture>>
CALLBACK_CONTEXT_STORE = new ConcurrentHashMap<>(128);
public static final Map<String, Map<String, DefaultRequestFuture>> CALLBACK_CONTEXT =
CALLBACK_CONTEXT_STORE;2. Use
|
Thanks for the detailed review. I have updated this PR following Option 1:
Verified locally:
|
|
Thanks for the update. I re-reviewed commit 9eb2216. The private ConcurrentMap plus the There are still two changes I suggest making before approval:
Currently, every thread that initially observes a missing connectionId calls Please return the existing context immediately for the losing path, for example: This is also cheaper because it avoids redundant size checks and trim iterations.
Once the store reaches its maximum size, every newly inserted connection context may One residual issue should also be documented or covered separately: |
|
@jay666mnj The previous redundant-trim issue is fixed, the public Map field descriptor is To avoid another sequence of incremental fixes, I suggest completing the PR with the
initContextIfNecessary() currently inserts an empty context and trims before The request can then repeatedly:
Since the key hash and the remaining map state are unchanged, this can repeat without Please pass the exact newly inserted context instance to the production trim method and The existing package-private trim(maxSize) overload can delegate with a null protected
Please iterate over map entries, retain both the candidate key and expected context, and If this returns false, retry victim selection. An unconditional remove(key) can delete a Together, protected-context selection and conditional outer removal provide a clear
The current condition: incorrectly reports a non-empty eviction as empty whenever the WARN is suppressed by the Please return immediately for empty contexts and independently apply the WARN gate: There is no need to log every empty-context eviction.
An AtomicLong failedFutureCountSinceLastWarn is sufficient. Increment it before applying This remains O(1) memory, retains no connectionId collection, creates no scheduled task,
Please add tests that verify:
The current registration/trim test submits the trim task last to the same fixed-size I do not suggest expanding this PR to redesign DefaultRequestFuture completion, Finally, JRaft 1.4.1 contains Raft behavior changes in addition to dependency cleanup, With the above code changes, tests, and dependency/CP validation completed, I do not see |
nacos-community
left a comment
There was a problem hiding this comment.
Summary
Upgrades SOFA-JRaft to 1.4.1 and replaces the removed ConcurrentLinkedHashMap-based callback store with a ConcurrentHashMap plus manual trim; the dependency bump itself is fine, but the replacement eviction path introduces concurrency and ACK-lifecycle regressions in a core module, so changes are requested before merge.
Findings
- [Warning] core/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java:148 — at the capacity limit,
trimCallbackContextIfNecessarycan evict thenewContextthat was just published;syncCallbackthen removes its own registration and retries without a progress guarantee, so a push can spin indefinitely instead of registering. Exclude the new context from eviction or evict an existing context before publishing it. - [Warning] core/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java:175 — iterating
ConcurrentHashMapkeys selects an arbitrary context, replacing the previous LRU eviction behavior; an active connection with pending ACKs can be failed while idle contexts remain. Preserve a recency/idle eviction policy or explicitly document and test this semantic change. - [Warning] core/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java:182 — the unconditional
remove(connectionId)can delete a context recreated afterclearContextbetweeniterator.next()and this call, then fail that replacement's live ACK futures. Iterate entries and useremove(connectionId, expectedContext)before failing futures.
Suggestions
- Make eviction select an entry and conditionally remove that exact map instance before completing any futures.
- Add deterministic tests with an injectable capacity to cover self-eviction during registration and clear/recreate racing with trim.
- Retain or deliberately replace the prior LRU behavior with a documented policy that avoids evicting active connections first.
Automated review by github-manager-bot
| if (existingContext != null) { | ||
| return existingContext; | ||
| } | ||
| trimCallbackContextIfNecessary(); |
There was a problem hiding this comment.
At the capacity limit, trim can evict the newContext just published here; syncCallback then removes its own registration and retries without a progress guarantee, so a push can spin indefinitely instead of registering. Exclude the new context from eviction or evict an existing context before publishing it.
|
|
||
| static void trimCallbackContextIfNecessary(int maxSize) { | ||
| while (CALLBACK_CONTEXT_STORE.size() > maxSize) { | ||
| Iterator<String> iterator = CALLBACK_CONTEXT_STORE.keySet().iterator(); |
There was a problem hiding this comment.
Iterating ConcurrentHashMap keys selects an arbitrary context, replacing the previous ConcurrentLinkedHashMap LRU eviction behavior; an active connection with pending ACKs can be failed while idle contexts remain. Preserve a recency/idle eviction policy or explicitly document and test this semantic change.
|
|
||
| int sizeBefore = CALLBACK_CONTEXT_STORE.size(); | ||
| String connectionId = iterator.next(); | ||
| Map<String, DefaultRequestFuture> removed = CALLBACK_CONTEXT_STORE.remove(connectionId); |
There was a problem hiding this comment.
This unconditional remove can delete a context recreated after clearContext between iterator.next() and this call, then fail that replacement's live ACK futures. Iterate entries and use remove(connectionId, expectedContext) before failing futures.
Please do not create a Pull Request without creating an issue first.
What is the purpose of the change
Upgrade SOFA-JRaft dependencies to 1.4.1 as part of the Nacos 3.3.0 dependency upgrade tracking in #15470.
Brief change
jraft-corefrom 1.4.0 to 1.4.1.rpc-grpc-implaligned through${jraft-core.version}.com.alipay.hessian.clhm.ConcurrentLinkedHashMapusage inRpcAckCallbackSynchronizerwith JDK concurrent map handling.Verifying this change
mvn -pl core spotless:check -DskipTestsmvn -pl core -am -Dtest=RpcAckCallbackSynchronizerTest -Dsurefire.failIfNoSpecifiedTests=false testmvn -pl core -am -DskipTests compilemvn -pl core dependency:tree -Dincludes=com.alipay.sofa,com.caucho:hessian -DskipTestsFollow this checklist to help us incorporate your contribution quickly and easily:
[ISSUE #123] Fix UnknownException when host config not exist. Each commit in the pull request should have a meaningful subject line and body.mvn -B clean package apache-rat:check spotbugs:check -DskipTeststo make sure basic checks pass. Runmvn clean installto make sure unit-test pass. Runmvn clean test-compile failsafe:integration-testto make sure integration-test pass.