fix(ha): reconstruct exact leader exception type on forwarded commands - #5017
Conversation
Follow-up to #5014. When a Follower forwards a command to the Leader and the Leader returns an error, reconstructLeaderException now rebuilds the exact exception type instead of collapsing retryable subtypes onto their NeedRetryException supertype. - ConcurrentModificationException is reconstructed as itself (still a NeedRetryException subtype) so callers catching the specific type match. - LockTimeoutException (retryable) and TimeoutException (non-retryable) are kept distinct: a query-deadline TimeoutException must not become retryable. - Common command types (CommandExecutionException, CommandParsingException, ValidationException, SchemaException) are reconstructed faithfully too. - DuplicatedKeyException keeps its structured 3-arg reconstruction; unknown classes still fall back to TransactionException. No reflection: an explicit registry map is the one-line-per-type extension point. Strengthened the CME test to assert the exact type and added retryable LockTimeout / non-retryable Timeout tests (16 tests, all passing).
|
Tick the box to add this pull request to the merge queue (same as
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
🟢 Coverage 100.00% diff coverage · -7.22% coverage variation
Metric Results Coverage variation ✅ -7.22% coverage variation Diff coverage ✅ 100.00% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (10e98c5) 134787 100310 74.42% Head commit (7083d02) 166601 (+31814) 111958 (+11648) 67.20% (-7.22%) 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 (#5017) 16 16 100.00% 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.
There was a problem hiding this comment.
Code Review
This pull request refactors exception reconstruction on the follower side in RaftReplicatedDatabase. It introduces a registry of leader-side exception factories (LEADER_EXCEPTION_FACTORIES) to reconstruct the exact exception types (such as LockTimeoutException and TimeoutException) instead of collapsing them into generic supertypes. This preserves correct retry semantics and allows callers to catch specific exception types. Additionally, unit tests have been added to verify the reconstruction behavior and retry eligibility of these exceptions. I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Review: fix(ha): reconstruct exact leader exception type on forwarded commands Overall this is a clean, well-scoped follow-up to PR 5014. Replacing the flattening if-chain with an explicit Correctness (verified)
The self-flagged line ~397 branch is a real dead branch (worth a tracked follow-up) Minor / optional
Tests Nice, surgical change. The only thing I would ask before merge is filing the line-397 follow-up so that latent dead branch stays visible. |
Codacy flagged the static field declared mid-class before its method. Move it up next to HTTP_CLIENT with the other static fields; no behavior change, 16 tests still passing.
Review: fix(ha): reconstruct exact leader exception type on forwarded commandsSolid, well-scoped follow-up to #5014. I verified the core claims against the codebase and the change is correct. 👍 Correctness (verified)
Design / security
Tests
Minor observations (non-blocking, mostly future-proofing)
Nothing here blocks merge. Nice, careful change with tests that pin the exact semantics. |
Address non-blocking review feedback on #5017: - Add CommandSQLParsingException, CommandSemanticException and QueryNotIdempotentException to the registry so command-path errors keep their exact type on the follower (a caller catching CommandParsingException now also matches its SQL/semantic subtypes) instead of collapsing to a generic TransactionException. Limited to ArcadeDBException subtypes with a (String) constructor: non-ArcadeDBException types would not be caught by the catch(ArcadeDBException) on the forward path anyway. - Switch Map.of -> Map.ofEntries to remove the 10-pair cap as the registry grew. - Add tests for the SQL-parsing subtype and QueryNotIdempotentException (18 tests).
Review: reconstruct exact leader exception type on forwarded commandsReviewed the diff against Correctness - verified ✅
Security ✅Avoiding reflection here is the correct instinct - never instantiate an arbitrary class name from a network response. The explicit allow-list map is the safe pattern. The flagged line-435 branch - confirmed a real dead branchThe PR note is accurate and I'd argue it's close enough to fix here rather than defer. Confirmed:
So the phase-2 "page version conflict / stepping down" SEVERE log branch can never be taken - it always falls through to the generic message. This is the exact same import-shadowing hazard this PR works around in the map (via FQN). A one-line fix - referencing Minor / optional
Test coverage ✅Good targeted coverage of the behaviors that matter: exact-type preservation, retryable vs non-retryable timeout distinction, and subtype-catch matching. Nice, focused change. Only substantive suggestion is to consider folding the line-435 FQN fix into this PR since it's the same class of bug. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5017 +/- ##
============================================
- Coverage 65.43% 65.43% -0.01%
+ Complexity 825 822 -3
============================================
Files 1687 1687
Lines 134787 134798 +11
Branches 28825 28822 -3
============================================
+ Hits 88203 88205 +2
- Misses 34514 34520 +6
- Partials 12070 12073 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
#5017) Follow-up to #5014. When a Follower forwards a command to the Leader and the Leader returns an error, reconstructLeaderException now rebuilds the exact exception type instead of collapsing retryable subtypes onto their NeedRetryException supertype. - ConcurrentModificationException / LockTimeoutException stay retryable NeedRetryException subtypes; non-retryable TimeoutException stays distinct. - Command-path types (CommandExecution/CommandParsing/CommandSQLParsing/CommandSemantic/QueryNotIdempotent/Validation/Schema) are reconstructed faithfully via an explicit registry (no reflection). - DuplicatedKeyException keeps its structured 3-arg reconstruction; unknown classes fall back to TransactionException. - Tests pin the retryable vs non-retryable semantics (18 tests). Follow-up issue #5018 tracks an unrelated latent dead branch spotted during review. (cherry picked from commit ef0fdb3)
Follow-up to #5014 (customer report: exceptions from the Leader not propagated as their real type on forwarded commands).
#5014 reconstructs a handful of leader-side exceptions on the Follower, but collapses retryable subtypes onto their
NeedRetryExceptionsupertype. A caller doingcatch (ConcurrentModificationException)(the specific subtype) would then miss it, and - more importantly - the retryable/non-retryable timeout distinction was lost.What this does
RaftReplicatedDatabase.reconstructLeaderExceptionnow rebuilds the exact leader-side type via an explicit registry (LEADER_EXCEPTION_FACTORIES) instead of an if-chain that flattens types:ConcurrentModificationExceptionis reconstructed as itself (still aNeedRetryExceptionsubtype, so retry logic is unchanged) so specific-type catches match.LockTimeoutException(retryable) andTimeoutException(non-retryable) are kept distinct - a query-deadlineTimeoutExceptionmust not be turned into something retryable.CommandExecutionException,CommandParsingException,ValidationException,SchemaException) are reconstructed faithfully too.DuplicatedKeyExceptionkeeps its structured 3-arg reconstruction; unknown classes still fall back toTransactionException.ArcadeDB's
ConcurrentModificationExceptionis referenced by FQN in the map because this file importsjava.util.ConcurrentModificationException.Tests
RaftReplicatedDatabaseTest: strengthened the CME test to assert the exact type, addedreconstructLeaderExceptionLockTimeoutIsRetryableandreconstructLeaderExceptionTimeoutIsNotRetryable. 16 tests, all passing.Note (not addressed here)
RaftReplicatedDatabaseline ~390 doesif (e instanceof ConcurrentModificationException)where the name resolves to the JDKjava.utiltype (via the existing import), while the engine throws ArcadeDB'scom.arcadedb.exception.ConcurrentModificationExceptionfor page-version conflicts. That may be a latent dead branch in the phase-2 "step down" log path - unrelated to this change, flagged for a separate look.