Repository navigation
Conversation
Join the receive routine before State.Wait returns and cancel its context on explicit Stop. This prevents node cleanup racing in-flight consensus file writes. Add a regression test for the shutdown contract. Co-Authored-By: OpenAI Codex GPT-6 <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: dashpay/tenderdash/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
✅ Final review complete — no blockers (commit b8a6beb) · triage: normal |
Port the focused lifecycle core from #1515, replacing the local State wait workaround while retaining other service ownership mechanisms. Co-Authored-By: OpenAI GPT-6 <noreply@openai.com>
There was a problem hiding this comment.
Claudius review — verdict: Request changes. 15 findings across 3 reviewers (0 CRITICAL, 0 HIGH, 5 MEDIUM, 8 LOW, 2 INFO). One LOW-severity finding is classified blocking because it plausibly trips the G-GROWTH gate (unbounded resource growth). The fourth planned reviewer (project-reviewer-adams) was skipped per this pipeline's early-stop rule once the security reviewer's own gate returned a blocking candidate.
Blocking — please confirm before merge:
- SEC-008 —
internal/libs/autofile/group.go:82-86:OpenGroup's headAutoFileis now opened withcontext.WithoutCancel, so its goroutine, file descriptor, and SIGHUP registration are released only by an explicitClose(). In-tree WAL usage looks covered (traced everyOnDrain/Waitcall site, found no droppedGroup), but if any rotation or reopen path ever skipsClose(), this leaks per-occurrence and grows unboundedly over a long-running validator's life. Please confirmClose()is unconditionally reached on every group rotation before merging — this is exactly the kind of thing static reading alone can't fully settle.
Worth a maintainer's close look (non-blocking, but not to be waved away):
- SEC-001 — the refactor now cancels a service's context before calling
OnStop()(previously the reverse). This defeats the pre-PR safeguard that let an in-progress consensusApplyCommitfinish before the timeout ticker stopped, with no replacement guard added. Caveat, in fairness: the guard may already have been partly defeated by pre-existingReactor.OnStopordering, so this could narrow an existing gap rather than open a brand-new one — but it's exactly the kind of shutdown-correctness regression a PR titled "fix consensus shutdown" shouldn't be introducing. - CALL-001 — that same cancel-before-OnStop reordering is a contract change hitting every
service.Implementationin the tree (~30 embedders), of which this PR audited and bridged only 2 (blocksync synchronizer, p2p connection). SEC-006/SEC-007 found a third unmigrated call site (p2p/router.go'sroutePeer) with a real, if low-impact-in-production, behavioral delta. - DOC-001 — this is a breaking change (
fix(service)!:) to a public package's runtime contract with noUPGRADING.mdentry. - SEC-002 — an unchecked type assertion in
blocksync/synchronizer.go:255that would panic ifOnStartis ever invoked outside itsStart()wrapper; the same PR already uses the safe comma-ok idiom for the equivalent case inconnection.go, so this is inconsistency more than a proven live bug. - SEC-003 —
SwitchToConsensusnow blocks uninterruptibly because the caller's context is discarded after admission; a wedged WAL replay can no longer be cancelled by its caller.
Also flagged (LOW/INFO, no action required to merge): two stale godocs left behind by the refactor (ticker.go, wal.go), an architecture-doc gap on the Stop-during-startup case, an unsynchronized field write in State.OnStart's rollback path, a nil-channel-block edge case in the new Stopping(), and — credit where due — the new lifecycle_test.go suite is genuinely solid engineering, not padding.
Full detail in the report.json/report.html CI artifacts.
📊 View full HTML review report
|
Checked all five open threads and the full 15-finding report against 8e431fc. No code changes in this verification pass. The five threads remain open; individual assessments are in replies. The reported rotation leak is not substantiated: Group.rotateFile retains the same AutoFile, closes its current descriptor under lock, and reopens lazily. It does not allocate a new worker or SIGHUP registration per rotation. WAL OnDrain calls Group.Close; failed WAL startup and repair also close/join their old group. Explicit Close ownership should be documented, and logjack should defer Close, but its process-exit cleanup gap does not establish unbounded validator growth or a merge blocker. Remaining small items: stale ticker/WAL hook comments and the architecture document's missing startup-stop exception are valid. The synchronizer comment should say parent lifetime: the captured parent can be the blocksync reactor, so manual reactor shutdown also cancels application. The rollback data-race claim is not demonstrated by a concurrent reader: startup failure precedes receiveRoutine admission, and BaseService defers OnStop while startup is active. A custom successfully stopped ticker must be recreated for retry, as the architecture document already requires for composite children. A lock around assignments alone would not establish reader synchronization. The Stopping nil-channel behavior is documented; a blocked ScheduleTimeout on an unstarted ticker is an API edge case requiring a concrete supported call path before treating it as a regression. The substantive remaining concerns are the global cancellation-contract audit (including router shutdown notifications), in-flight consensus finalization policy, handoff caller-cancellation policy, and upgrade guidance. Static verification only; no new tests were run. 🤖 Co-authored by Claudius the Magnificent AI Agent |
Keep processing on the State parent's lifetime while canceling intake, and let handoff callers cancel their wait without abandoning reactor-owned work. Document the global cancellation contract, cleanup ownership and migration boundaries; harden context lookup and close logjack's group on return. Co-Authored-By: OpenAI GPT-6 <noreply@openai.com>
|
Addressed the actionable feedback in ec8d306:
The two main regressions were red before the fix, then green. Affected package tests pass with race/deadlock enabled; the lifecycle regression subset passed 10 repetitions. Final consensus/node tests and scoped lint pass (0 lint issues). New CI is pending. The speculative per-rotation resource-growth finding is not reproduced: rotation reuses one AutoFile and closes its descriptor; final Close joins its worker. No speculative rollback locks or Stopping contract changes were introduced. 🤖 Co-authored by Claudius the Magnificent AI Agent |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The lifecycle changes correctly coordinate worker admission, cancellation, draining, consensus handoff ownership, and file cleanup within the PR's stated partial-migration scope. Source inspection and the shutdown regressions support the supplied clean findings; no actionable in-scope defects were confirmed. All seven affected packages passed a fresh race/deadlock-enabled test run using a compatible BLS build, and the worktree remains unchanged.
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: tenderdash-consensus-security); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: tenderdash-consensus-security); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
normalbygpt-6.1-sol(effort low) — The diff introduces intricate, cross-cutting worker lifecycle and shutdown synchronization changes in libs/service/service.go and consensus integration, but does not change consensus rules, cryptography, funds handling, peer-facing deserialization, or storage migrations. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— tenderdash-consensus-security (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 13% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— tenderdash-consensus-security (completed, effort high); agentphase2-reviewer
|
Bots are done — your move: post |
Consensus shutdown now waits for its receive loop and file workers before node teardown releases their resources. This is the focused lifecycle-core extraction from #1515.
Issue being fixed or feature implemented
Node shutdown could return while consensus still used its data directory, causing teardown failures such as
directory not empty. A shared worker lifecycle replaces the consensus-only cancellation and WaitGroup workaround.What was done?
This PR is independent of #1515 and carries only its core. RPC/WebSocket ownership, mempool recheck drain, broad P2P/ABCI/reactor migrations and the broader durable-finalization/dependency-lifetime changes remain outside this PR. Plain goroutines are not automatically tracked; this does not promise that Node.Wait joins every repository helper.
How Has This Been Tested?
go test -race -tags=deadlock -p 1: service, autofile, consensus, node, blocksync and P2P connections.dashcore,rotate) passed on 8e431fc. Follow-up CI results are pending; this remains a draft.Breaking Changes
Manual Stop cancels the context passed to OnStart. Stop invokes OnStop synchronously but registered work is joined by Wait; callers must Stop then Wait before releasing worker resources. Hooks run outside the lifecycle lock and must not wait for their own registered work. TimeoutTicker implementations now require Wait. Failed startup may retry after worker cleanup; successfully stopped services cannot restart.
Checklist:
For repository code-owners and collaborators only
🤖 Co-authored by Claudius the Magnificent AI Agent
PR Hygiene ·
b8a6beb/self-reviewedWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.