Repository navigation
fix(#9556): stop the Raft log cache-eviction thread before closing the Ratis server - #9565
Conversation
…e Ratis server Ratis 3.3.1 SegmentedRaftLog.close() holds the log's write lock while it joins the cache-eviction thread, which can be parked waiting for that same lock (RATIS-2719, fixed upstream but not yet released). RaftHAServer now closes the eviction thread first, with a bounded wait, before each of its three Ratis server closes, which is what the upstream fix does inside close(). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again. |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 1 high |
🟢 Metrics 16 complexity
Metric Results Complexity 16
🟢 Coverage 65.96% diff coverage · -5.63% coverage variation
Metric Results Coverage variation ✅ -5.63% coverage variation Diff coverage ✅ 65.96% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (169e31e) 221833 189852 85.58% Head commit (a8b40db) 254497 (+32664) 203482 (+13630) 79.95% (-5.63%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#9565) 47 31 65.96% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds a bounded cache-eviction stop utility and calls it before Ratis server close during restart and shutdown. Tests cover the close behavior, and native-image metadata registers the reflected field. ChangesRaft server close handling
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant RaftHAServer
participant RaftLogCacheEviction
participant RatisDivisions
participant EvictionThread
participant RatisServer
RaftHAServer->>RaftLogCacheEviction: stopBeforeClose(server)
RaftLogCacheEviction->>RatisDivisions: Read divisions and logs
RaftLogCacheEviction->>EvictionThread: Close cache eviction and wait with timeout
RaftHAServer->>RatisServer: Close server
Merge Risk: ⚪ Minimal · up to Before closing the Raft server during restart or shutdown, the server now stops the log cache-eviction thread, waiting at most 10 seconds. This avoids a known shutdown deadlock without adding a new blocking path, and no concrete problem was found in the change. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)✅ Passed checks (4 passed)Full details: Docstring CoverageExplanation Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 24 functions across 3 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ReviewWell-scoped, well-documented workaround. Closing the eviction Strengths
Minor suggestions
Risk
No blocking concerns. LGTM once CI is green. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9565 +/- ##
=========================================
Coverage 77.36% 77.36%
- Complexity 4165 4173 +8
=========================================
Files 2157 2158 +1
Lines 221833 221880 +47
Branches 47521 47526 +5
=========================================
+ Hits 171611 171649 +38
- Misses 32020 32021 +1
- Partials 18202 18210 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Closes #9556
Apache Ratis 3.3.1
SegmentedRaftLog.close()takes the log's write lock and then joins the cache-eviction thread. If that thread was woken just before (by a cache miss or a segment roll), it may be parked inwriteLock()insidecheckAndEvictCache(), andReentrantReadWriteLock.lock()ignores the interrupt. The close then hangs forever while holding the write lock.RaftHAServernow calls the newRaftLogCacheEviction.stopBeforeClose(server)before each of its three Ratis server closes. The helper reflectively closes each division log'scacheEvictionAwaitToRunwhile no lock is held, so the eviction thread can finish its pass and exit.AwaitToRun.close()is idempotent, so the close Ratis runs later has nothing left to join. This is the same reordering as the upstream fix, applied from outside. The join runs on its own daemon thread with a 10 s bound (STOP_WAIT_MS), so a write lock held elsewhere cannot hang the caller as well.The issue's to-do items
stop()callsraftServer.close()unbounded. The in-place restart bounds only the wait for a close already running elsewhere (awaitCloseInProgress), not its ownoldServer.close(). This PR removes the deadlock #9556 reported, and the eviction stop it adds is bounded. A general bounded close needs a design decision (a timed-out close may still hold the storage lock and gRPC ports), tracked in #9561.Completeness
Invariant: no ArcadeDB-initiated Ratis server close runs while that server's segmented log still has a live cache-eviction thread.
RaftHAServer.stop()(raftServer.close())oldServer.close()this.raftServer.close()reachability-metadata.json, guarded by a test)Reachability: the three call sites are on the live stop and restart paths.
stopBeforeCloseStopsTheEvictionThreadOfARunningServerdrives the helper against a real running single-nodeRaftServer.Known gaps
Test plan
Issue9556RaftLogCloseEvictionDeadlockTest(new, 6 tests):ratisCloseHangsWhenTheEvictionThreadWaitsForTheWriteLock: control case. It reproduces the hang deterministically on 3.3.1 (the closer takes the write lock, cache misses park the eviction thread on it, then close) and asserts the closer is stuck inAwaitToRun.close. It goes red when a Ratis upgrade carries RATIS-2719, and that is the signal to delete the workaround.closeCompletesWhenTheEvictionThreadWasStoppedFirst: the same sequence afterstop(), and close returns.stopGivesUpWhileAnotherThreadHoldsTheWriteLock: the bounded wait.stopBeforeCloseStopsTheEvictionThreadOfARunningServer: realRaftServer, the entry pointRaftHAServercalls.nativeImageRegistersTheEvictionField,stopIgnoresALogThatIsNotSegmented.stop()sabotaged into a no-op, 3 of the 4 fix-side tests fail.Issue9549RaftLogCacheBoundTestpasses.ha-raftunit suite: 2906 tests, 0 failures.Issue4839RecoveryRewiresStateMachineITandIssue5271ClosedDivisionRecoveryITpass.Issue8900ClosedServerInPlaceRestartITfails in teardown (DatabaseAreNotIdentical Types 8 <> 7): 1 of 3 methods on this branch, 2 of 3 withmain'sRaftHAServer. It already fails on main and is filed as Issue8900ClosedServerInPlaceRestartIT fails in teardown on main: DatabaseAreNotIdentical Types 8 <> 7 #9562.Adversarial pass
Not run: the subagent tool was not available in this session. In its place, a self-review of the corner cases:
beforeClose == CLOSING): the stopper's join also blocks. The caller gives up after 10 s, logs at WARNING and proceeds exactly as before, leaving one parked daemon thread behind. This does not make the existing behaviour worse.Review cycles
Deferred / skipped review items
AwaitToRunname is<peer>@group-<id>-cacheEviction-AwaitToRun(built fromRaftGroupMemberId), so the WARNING already names the division.System.getProperty("basedir", ".")would be more robust" - skipped to stay consistent withNativeImageReflectionMetadataTest, which resolves the same file the same way. Surefire runs from the module directory.@Tag("slow")" - not needed: the class runs in about 8 s.Final state: clean-approval
🤖 Generated with Claude Code
Summary by CodeRabbit