fix(logservice): start one HAKeeper ticker per store - #28696
Conversation
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? |
XuPeng-SH
left a comment
There was a problem hiding this comment.
Deep review completed at exact head 20fda8e against base 4a7bfac. No concrete blocker found. COMMENT only for the self-authored PR.
Both voting and non-voting startup paths share one store-lifetime admission guard, and sync.Once publishes the same admission result to concurrent callers. The guarded operation is only Stopper admission, not Raft/RPC work. RunNamedTask's returned failure is terminal shutdown rejection, so retaining it does not suppress a transient retry. Normal ticker execution survives missing/local-restarting replicas and reads the current atomic replica identity; its only normal early exit besides cancellation is task-worker admission rejection during shutdown. Store Close still cancels/joins both ticker workers before closing NodeHost. A fresh store owns fresh Once/error state, and disabled-worker fixtures bypass admission. This is an admission guarantee, not a new readiness, panic-recovery or bounded-callback guarantee; existing worker panics remain unrecovered and existing in-flight callback waits are unchanged.
The fixture changes remove competing manual/background drivers without replacing replicated clock semantics with a fake counter. The expiry test now checks no-CN retention, exact boundary, expired-versus-live task separation, repeated scheduling and returning-CN behavior; the asynchronous task-ticker case remains. Restart tests use real NodeHosts, prove voting-replica clock progress, join workers before counting entries and cover both replica roles. Cleanup order and FD inspection now use the actual initialized filesystem. No new hot-loop lock/allocation or accumulating worker generation found.
Evidence: full diff, startup/Stopper/ticker/task-worker/replica-stop/Close closures and test oracles inspected. The documented Linux race stress, owning-package normal/race runs and mutation counterexamples are reused as author-reported evidence; no new native test, retained-data upgrade or live SQL cluster run is claimed. CI was not awaited. Exact head/base rechecked before submission.
What type of PR is this?
Which issue(s) this PR fixes:
Fixes #28550. Completes the expired-task test follow-up documented in #28668.
What this PR does / why we need it:
HAKeeper state transitions need one driver per store. Two paths violated that ownership:
TestTaskSchedulerCanReScheduleExpiredTasksmanually bootstrapped and scheduled tasks while its fixture also ran the production HAKeeper checker and task ticker. This caused the allocator race reported by the #28646 UT job.Both replica startup paths now share one store-lifetime startup guard. Concurrent callers observe the same admission result; shutdown rejection is terminal for that store. Replica stop/resume reuses the original ticker, while a new store gets a fresh guard. This adds synchronization only at worker startup, with no new locks, scans, or allocations in tick, task scheduling, ID allocation, or query execution.
The manual task-service fixture now always disables background workers. Its shared bootstrap helper enforces that precondition, eliminating duplicated bootstrap code. The expired-task test advances the real replicated HAKeeper clock explicitly instead of sleeping for expiration. It retains the existing complete-and-truncate behavior for expired tasks and adds live-runner and exact-boundary controls. The explicit task-ticker test remains asynchronous and joins its worker before teardown. FD checks now inspect the initialized fixture filesystem.
Test plan / QA reference
TestTaskSchedulerCanReScheduleExpiredTasks: no eligible CN retains a Created task; exact expiry boundary retains the running task; the next tick removes only the expired runner's task; repeated scheduling preserves the live task; a returning CN accepts new work without resurrecting the old task.TestTaskSchedulerCanScheduleTasksToCNs,TestTickerForTaskSchedule,TestAllocateIDByKeyWithRequestID. All use the checked shared helper; the ticker case still proves asynchronous assignment.TestHAKeeperTickerSurvivesReplicaRestart: concurrent startup admission; real voting and non-voting stop/resume; replicated tick progress before and after voting-replica restart; exactly one worker entry after joining all workers.TestHAKeeperTickerRejectsStartupAfterShutdown: concurrent callers receivestopper.ErrUnavailable; disabled-worker fixtures create no worker.<to<=, disabling expiry entirely, or re-enabling the manual fixture's background workers each fail the corresponding assertion. These deliberately incorrect variants are not committed.Validation on Linux/amd64, Go 1.26.4, using source-matched native artifacts and
.agents/skills/mo-dev/scripts/mo-cgo-test:-race -count=100in a single process. Their worker-disabled execution paths are unchanged by the subsequent production startup guard.-race -count=68; shutdown admission passed-race -count=100. Counts follow a 30-second per-test stress budget (restart measured 0.44 seconds).pkg/logservicenormal suite passed (110.622 seconds); the full race suite passed (112.295 seconds), both with-count=1 -timeout=600s.golangci-lint --new-from-rev=<base> ./pkg/logservice, packagemolint, andgit diff --checkpassed.The SQL surface and wire/disk formats do not change. The affected restart boundary is exercised with real in-memory NodeHosts; no full SQL cluster, retained-data upgrade, or 55-machine test is claimed.
Rebased onto main
4a7bfac4f9before delivery. The intervening commit changes onlypkg/embed, outside the verifiedpkg/logservicetest dependency graph; native inputs and all validated code/test paths are unchanged, so the completed evidence is reused.Ownership / unhappy-path review
sync.Onceadmission per store; both replica roles share it; no RPC is held under the guard.