fix(txn): restore recovery routes and cancellation#25956
Conversation
Resolve incomplete participant routes before recovery RPCs and make the full TN startup recovery chain cancellable during shutdown. Co-authored-by: ULookup <CHBulookup@outlook.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
gouhongshen
left a comment
There was a problem hiding this comment.
Codex automated review
Two blocking recovery lifecycle regressions remain. Focused package and race tests pass but do not cover them.
P1 - Cancellation of a MEMKV log read panics the TN (pkg/txn/storage/mem/kv_txn_storage.go:145)
startRecovery now passes the cancellable recovery context into StartRecovery. During replica shutdown, CancelRecovery can therefore cancel an in-flight MEMKV logClient.Read; the real log-service RPC future returns ctx.Err() on cancellation. This code panics on every read error, so shutting down a MEMKV-backed TN while its recovery read is slow/unavailable crashes the process instead of terminating recovery. The added test only cancels a full channel send, not an in-flight read. Treat context cancellation as normal producer termination.
P1 - A non-coordinator replica now blocks startup on unrelated participant routes (pkg/txn/service/service_recovery.go:50)
Route resolution runs for every recovered prepared/committing record before addLog. But end only sends recovery RPCs when this service is the transaction coordinator (txnMeta.TNShards[0]). For a non-coordinator B in [A, B, C], if C is absent from the snapshot, B now retries forever in route resolution even though B will never issue a recovery RPC; on origin/main it added the log and end skipped recovery work, allowing B to start. This makes B unavailable for unrelated work while another participant is missing. Resolve/wait only on the coordinator path and add a non-coordinator missing-route regression test.
XuPeng-SH
left a comment
There was a problem hiding this comment.
Deep re-review completed on exact head a4d302b. No blocking or non-blocking correctness finding remains.
I re-audited the complete diff and every previously reported edge:
- recovery first folds the complete durable stream, then resolves routes only for surviving coordinator-owned Prepared/Committing transactions;
- one authoritative TN snapshot is reused per recovery pass, with refresh/retry only when a required route is absent;
- missing/stale/transient routes, cluster readiness, retry timers, MEMKV reads and bounded channel publication all terminate through the recovery context;
- store shutdown cancels both create and recovery before stopper join, including replicas admitted after the initial Range through stopper cancellation propagation;
- the reserveStart to service publication window terminates through serviceC or startedC, and close remains idempotent with storage kept alive until RPC drain.
Q1 partial construction: service publication and startup completion both have explicit terminal signals. Q2 blocking operations: every new wait has recovery/store cancellation. Q3 partial cleanup: recovery completion is once-guarded; replica close remains the single service/storage cleanup owner.
Fresh validation on this exact head:
- all tests in clusterservice, txn/service, txn/storage/mem and tnservice;
- full pkg/txn/... suite;
- all new route/fold/cancel/late-generation regressions under race, count 20;
- affected package build and vet;
- clean merge with current main c0486a2, followed by the four package suites and the same race matrix at count 10;
- PR diff-check clean.
The current-head CI jobs are still running, so this approval does not claim those unfinished jobs passed.
aptend
left a comment
There was a problem hiding this comment.
Request changes: one P1 shutdown closure gap remains on exact head a4d302b.
P1 — Store.Close still hangs when the cluster never becomes ready
Store.Close now correctly cancels replica recovery before joining the store stopper, but it then calls s.moCluster.Close(). The built-in cluster.Close() executes waitReady() before stopper.Stop(). If the initial HAKeeper GetClusterDetails calls keep failing, readyC is never closed, so shutdown blocks forever at moCluster.Close() and cannot stop the refresh task that owns the only path to readiness.
I reproduced this on the exact head with a cluster client that continuously returns a HAKeeper error and a 10 ms refresh interval: Close was still blocked after 100 ms and returned only after readyC was manually released. This leaves the PR stated shutdown invariant incomplete for the cluster-metadata-unavailable case.
Please make cluster shutdown cancel/stop the refresh task before waiting for readiness, and explicitly release readiness waiters (or model a separate closed state). Add an end-to-end regression where the initial cluster refresh never succeeds and Store.Close still terminates.
Relevant paths:
matrixone/pkg/tnservice/store.go
Lines 240 to 252 in a4d302b
matrixone/pkg/clusterservice/cluster.go
Lines 368 to 371 in a4d302b
Other validation on this head passed: affected package tests, focused recovery/lifecycle tests under -race -count=3, and go vet.
|
Addressed the latest shutdown CR in commit The failure reproduced on The fix now stops the cluster refresh/GC tasks first and then uses Added Validation on
I also re-audited Q1–Q3 for cluster shutdown: both refresh and close terminate |
aptend
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 3cee83bf4d. The previous P1 shutdown gap is fixed: cluster close now stops refresh/GC tasks before releasing readiness waiters through readyOnce, while the bounded force-refresh channel remains safe for concurrent/repeated close paths.
Validation passed: affected-package tests, focused lifecycle/recovery tests under -race -count=5, go vet, and git diff --check. No remaining findings.
Merge Queue Status
This pull request spent 22 minutes 37 seconds in the queue, with no time running CI. Waiting for
All conditions
ReasonPull request #25956 has been dequeued Pull request from fork cannot be queued. This pull request comes from a fork, and Mergify needs the author's permission to update its branch.
Failing checks:
HintYou should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it. Tick the box to put this pull request back in the merge queue (same as
|
What type of PR is this?
Which issue(s) this PR fixes:
Fixes #25951
What this PR does / why we need it:
Recovered prepared/committing transaction metadata may contain only durable participant identity. Sending recovery RPCs with that metadata currently uses an empty
ReplicaIDand address. At the same time, a missing participant can makeTxnService.Start()wait indefinitely, while replica or store shutdown waits forStart()before it can close the service.This PR establishes one recovery invariant: recovery RPCs use complete current participant routes, and TN shutdown can terminate every recovery producer, consumer, route wait, and blocked
Start().ShardIDthrough the current TN cluster snapshot beforeGetStatusorCommitTNShard; preserve a durableLogShardIDwhen the snapshot omits it.Start().cancelStartandCancelRecoveryto replicas before stopping the store task group; keep storage open until RPC drain completes.Store.Close()does not depend on metadata readiness.ctx.Done().Compatibility and non-goals:
Validation:
go test ./pkg/clusterservice ./pkg/txn/service ./pkg/txn/storage/mem -count=1go test ./pkg/tnservice -count=1go test ./pkg/txn/... -count=1-racego vet ./pkg/clusterservice ./pkg/txn/service ./pkg/tnservice ./pkg/txn/storage/mem ./pkg/vm/engine/test/testutilgo test ./pkg/... -run '^$' -count=1git diff --checkDiff size: 8 production/interface/helper files / about 260 added handwritten lines; 6 test files / about 850 added test lines; no generated code.
Co-authored-by: ULookup CHBulookup@outlook.com