Skip to content

server, dispatcher: improve node liveness self fence#5106

Merged
ti-chi-bot[bot] merged 14 commits into
pingcap:masterfrom
asddongmen:0520-improve-node-liveness-self-fence
Jun 12, 2026
Merged

server, dispatcher: improve node liveness self fence#5106
ti-chi-bot[bot] merged 14 commits into
pingcap:masterfrom
asddongmen:0520-improve-node-liveness-self-fence

Conversation

@asddongmen

@asddongmen asddongmen commented May 20, 2026

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: close #5202

When a TiCDC capture loses its etcd session or lease, it can no longer prove that it still owns local dispatcher work. The previous shutdown path did not immediately fence local write paths, so a stale capture could still accept maintainer requests or continue writing downstream while failover was already in progress. This created a short but unsafe window for duplicate or out-of-date downstream writes before normal cleanup finished.

What is changed and how it works?

This PR adds a local fence path for session-done and lease-expired events. The server watches the etcd session, triggers local fencing before shutdown, and the dispatcher orchestrator stops accepting new maintainer requests. Dispatcher managers cancel local write paths immediately, close local dispatchers asynchronously, and continue cleanup without waiting for progress draining. The redo cleanup path is also guarded so partially initialized managers do not panic when fencing. New integration coverage simulates capture session loss and verifies the local fence behavior and downstream consistency.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test: dev-machine MySQL integration cases and GitHub /test all

Questions

Will it cause performance regression or break compatibility?

No. The new path only runs when a local capture loses its session or is being fenced.

Do you need to update user documentation, design documentation or monitoring documentation?

No.

Release note

Locally fences stale captures after etcd session loss to avoid unsafe downstream writes.

Summary by CodeRabbit

  • New Features

    • Added server-side etcd session watchdog and explicit local-fence controls to quickly close local write paths across orchestrator and managers.
  • Bug Fixes

    • Prevented races during dispatcher/redo creation, merge and shutdown; avoid registering partially-initialized dispatchers and short-circuit operations when the write path is fenced.
    • Improved close lifecycle to ensure single close launch and reliable finalization.
  • Tests

    • Added unit and integration tests covering fencing, shutdown, and session-watchdog scenarios.

@ti-chi-bot

ti-chi-bot Bot commented May 20, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-linked-issue do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels May 20, 2026
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds coordinated local fencing: DispatcherManager write-path fencing and lifecycle refactor, DispatcherOrchestrator fenced mode and propagation, a server session watchdog that triggers local fence on etcd lease/session loss, unit/integration tests, helper updates, and related CI/script/failpoint changes.

Changes

DispatcherManager: core fencing and lifecycle

Layer / File(s) Summary
DispatcherManager: core fencing and lifecycle
downstreamadapter/dispatchermanager/dispatcher_manager.go
Adds IsWritePathClosedError, LocalFence(), writePathMu/writePathClosed, CAS-based close/refactor, and fencing-aware sink/sharedInfo/heartbeat registration and event-dispatcher flows.
DispatcherManager: redo dispatcher fencing
downstreamadapter/dispatchermanager/dispatcher_manager_redo.go
Make redo-component init, redo dispatcher creation/merge, and table-trigger redo initialization fencing-aware with writePathMu checks and early returns.
Helpers and EventDispatcher API
downstreamadapter/dispatchermanager/helper.go, downstreamadapter/dispatcher/event_dispatcher.go
Short-circuit fenced create errors, add deleteCreatedOperators, route checkpoint forwarding via manager, and add shouldStop callback to EmitBootstrap.
Errors and sink failpoint
pkg/errors/error.go, pkg/sink/mysql/mysql_writer_dml_exec.go
Add ErrDispatcherManagerWritePathClosed and expand MySQL sink failpoint logging/hang block.
DispatcherManager tests
downstreamadapter/dispatchermanager/dispatcher_manager_test.go
Add schema-store test stub and multiple tests validating LocalFence behavior, fencing during init, checkpoint skipping, redo-safety, and create-operator preservation.

DispatcherOrchestrator: fenced mode and manager tracking

Layer / File(s) Summary
Orchestrator: fenced flag and request handling
downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator.go
Add fenced flag, drop incoming/queued maintainer messages when fenced, and initialize initializingDispatcherManagers.
Orchestrator: manager lifecycle & LocalFence
downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator.go
Track initializing managers during NewDispatcherManager, treat IsWritePathClosedError as early return, publish or immediately fence created managers, and implement LocalFence() to fence and close tracked managers/shards.
Orchestrator tests
downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go
Tests for LocalFence dropping new messages and immediately fencing published and initializing managers.

Server: session watchdog

Layer / File(s) Summary
Server session watchdog
server/server.go
Add etcd session/lease watchdog, localFencer interface, localFenceOnce, watchdog goroutine, watchEtcdSession, and localFence invocation on session/lease loss.
Server watchdog tests
server/server_session_watchdog_test.go
Tests asserting fencing on session done, expired lease, and idempotent localFence behavior.

Integration tests and CI

Layer / File(s) Summary
Integration test script and conf
tests/integration_tests/capture_local_fence_on_session_done/*
Add new end-to-end test script and diff_config to validate local fence on lease revocation with MySQL sink hang failpoint.
Integration log/CI updates
tests/integration_tests/*, tests/integration_tests/run_light_it_in_ci.sh
Adjust log assertions and add new test to mysql light CI group.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

lgtm

Suggested reviewers

  • hongyunyan
  • lidezhu
  • wk989898

"I nibble logs and guard the line,
When leases drop and sessions fade,
I fence the writes, keep data fine,
Quiet managers in evening shade.
A carrot hop for safe cascade." 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: improving node liveness through self-fencing when a capture loses its etcd session.
Linked Issues check ✅ Passed The PR directly addresses issue #5202 by implementing immediate local fencing on session loss, rejecting new maintainer requests, and preventing unsafe downstream writes during failover.
Out of Scope Changes check ✅ Passed All changes focus on implementing local fencing for session loss scenarios. Minor supporting changes (test configurations, log message updates) are directly related to the fencing feature.
Description check ✅ Passed The PR description includes all required sections from the template: problem statement with issue number, technical changes explanation, test coverage (unit, integration, manual), and answers to compatibility/documentation questions. Release note is provided.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ti-chi-bot ti-chi-bot Bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label May 20, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a local fencing mechanism to ensure that downstream writes are stopped immediately when a capture loses its etcd session or lease. This is achieved by adding a session watchdog in the server and implementing a LocalFence method across the DispatcherOrchestrator and DispatcherManager to bypass graceful draining in failure scenarios. The review feedback identifies potential nil pointer dereferences in the DispatcherManager's shutdown logic, specifically regarding the redoSink when redo logging is enabled.

Comment thread downstreamadapter/dispatchermanager/dispatcher_manager.go Outdated
Comment thread downstreamadapter/dispatchermanager/dispatcher_manager.go Outdated
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jun 4, 2026
@asddongmen asddongmen force-pushed the 0520-improve-node-liveness-self-fence branch from 4a22383 to fa0781c Compare June 4, 2026 12:53
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test pull-cdc-mysql-integration-heavy

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen asddongmen marked this pull request as ready for review June 5, 2026 06:14
@ti-chi-bot ti-chi-bot Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 5, 2026
@asddongmen asddongmen self-assigned this Jun 5, 2026
@ti-chi-bot ti-chi-bot Bot added the approved label Jun 5, 2026
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go (1)

458-462: ⚡ Quick win

Make the drop assertion deterministic.

Lines 458-462 only show that nothing reached processed within 50ms. A message that was merely delayed, or enqueued after fence but never drained, looks identical, so this can both mask regressions and flake on slow CI. Please assert the post-fence admission state directly instead of relying on a short timeout.

As per coding guidelines, **/*_test.go: Prefer focused deterministic tests; see docs/agents/testing.md before adding or changing tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go`
around lines 458 - 462, Replace the flaky 50ms timeout check on the processed
channel with a deterministic assertion of post-fence admission state: after
applying the local fence, directly assert the orchestrator/maintainer admission
flag or queue length (e.g. check an "isAdmitting" / "admissionOpen" boolean or
the maintainer queue length) to prove new maintainer messages are rejected, and
if only the processed channel is available, use a non-blocking select (case msg
:= <-processed: require.FailNow(...); default: ) and also attempt to enqueue a
new maintainer and assert that enqueue returns a failure/false or does not
increase the queue size; reference the processed channel and the local fence
operation in the test to locate where to make this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@downstreamadapter/dispatchermanager/dispatcher_manager.go`:
- Around line 60-77: Create a dedicated predefined repo error for the fenced
write-path state (e.g., add errors.ErrDispatcherWritePathClosed in the central
errors package) and replace the current message-substring approach: have
newWritePathClosedError() generate/return that specific error (use the repo
error construction consistent with other errors, e.g., FastGenByArgs on
errors.ErrDispatcherWritePathClosed), and update IsWritePathClosedError(err) to
detect the condition by matching that error directly (either via errors.Is(err,
errors.ErrDispatcherWritePathClosed) or comparing RFCCode to
errors.ErrDispatcherWritePathClosed.RFCCode()) instead of checking for
ErrChangefeedInitTableTriggerDispatcherFailed plus a substring.

---

Nitpick comments:
In `@downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go`:
- Around line 458-462: Replace the flaky 50ms timeout check on the processed
channel with a deterministic assertion of post-fence admission state: after
applying the local fence, directly assert the orchestrator/maintainer admission
flag or queue length (e.g. check an "isAdmitting" / "admissionOpen" boolean or
the maintainer queue length) to prove new maintainer messages are rejected, and
if only the processed channel is available, use a non-blocking select (case msg
:= <-processed: require.FailNow(...); default: ) and also attempt to enqueue a
new maintainer and assert that enqueue returns a failure/false or does not
increase the queue size; reference the processed channel and the local fence
operation in the test to locate where to make this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54d91177-8133-4394-a936-6f0327fa9be2

📥 Commits

Reviewing files that changed from the base of the PR and between 5e97fd8 and ae413aa.

📒 Files selected for processing (8)
  • downstreamadapter/dispatchermanager/dispatcher_manager.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_redo.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_test.go
  • downstreamadapter/dispatchermanager/helper.go
  • downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator.go
  • downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go
  • tests/integration_tests/capture_local_fence_on_session_done/run.sh
  • tests/integration_tests/capture_session_done_during_task/run.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/integration_tests/capture_local_fence_on_session_done/run.sh
  • downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator.go

Comment thread downstreamadapter/dispatchermanager/dispatcher_manager.go
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
downstreamadapter/dispatchermanager/dispatcher_manager_redo.go (1)

338-340: Document fenced no-op in UpdateRedoMeta.

DispatcherManager.UpdateRedoMeta (downstreamadapter/dispatchermanager/dispatcher_manager_redo.go:338-340) returns immediately when writePathClosed is set with no error/log. The only observed call site (downstreamadapter/dispatchermanager/helper.go:751-752) ignores the outcome, so callers can’t tell whether the meta was updated. Add a brief comment above the early return explaining this is an intentional no-op during write-path shutdown/fencing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@downstreamadapter/dispatchermanager/dispatcher_manager_redo.go` around lines
338 - 340, UpdateRedoMeta currently returns immediately when
e.writePathClosed.Load() is true with no indication to readers; add a brief
comment directly above the early return in function UpdateRedoMeta explaining
that this is an intentional no-op because the write path is closed/fenced during
shutdown so metadata updates are ignored (do not change behavior or add an
error/log, just document the intent).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@downstreamadapter/dispatchermanager/dispatcher_manager_redo.go`:
- Around line 338-340: UpdateRedoMeta currently returns immediately when
e.writePathClosed.Load() is true with no indication to readers; add a brief
comment directly above the early return in function UpdateRedoMeta explaining
that this is an intentional no-op because the write path is closed/fenced during
shutdown so metadata updates are ignored (do not change behavior or add an
error/log, just document the intent).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3fbd800b-16e6-49af-9547-7a196144a232

📥 Commits

Reviewing files that changed from the base of the PR and between e7cda87 and a05f186.

📒 Files selected for processing (5)
  • downstreamadapter/dispatcher/event_dispatcher.go
  • downstreamadapter/dispatchermanager/dispatcher_manager.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_redo.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_test.go
  • downstreamadapter/dispatchermanager/helper.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • downstreamadapter/dispatchermanager/helper.go
  • downstreamadapter/dispatchermanager/dispatcher_manager.go

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/retest

1 similar comment
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/retest

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
downstreamadapter/dispatchermanager/dispatcher_manager_redo.go (1)

99-108: 💤 Low value

Redundant fence check after redo message registration.

The check at lines 106-108 occurs after RegisterRedoMessageDs has already completed at line 104. If the write path closes between lines 105 and 106, the registration has already happened. This differs from other fencing patterns where the check prevents the operation. Consider removing this redundant check or moving the registration inside the critical section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@downstreamadapter/dispatchermanager/dispatcher_manager_redo.go` around lines
99 - 108, The post-registration fence check is redundant; either remove the
second manager.writePathClosed.Load() check and its newWritePathClosedError()
return, or (preferred) move the
appcontext.GetService[*HeartBeatCollector](appcontext.HeartbeatCollector).RegisterRedoMessageDs(manager)
call inside the critical section protected by manager.writePathMu so the
registration is only performed while the write-path is provably open; then
release manager.writePathMu and keep the single pre-registration check using
manager.writePathClosed.Load() to decide whether to return
newWritePathClosedError().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@downstreamadapter/dispatchermanager/dispatcher_manager_redo.go`:
- Around line 99-108: The post-registration fence check is redundant; either
remove the second manager.writePathClosed.Load() check and its
newWritePathClosedError() return, or (preferred) move the
appcontext.GetService[*HeartBeatCollector](appcontext.HeartbeatCollector).RegisterRedoMessageDs(manager)
call inside the critical section protected by manager.writePathMu so the
registration is only performed while the write-path is provably open; then
release manager.writePathMu and keep the single pre-registration check using
manager.writePathClosed.Load() to decide whether to return
newWritePathClosedError().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e0fbe318-a1e2-47e4-b42a-b2f585006fd8

📥 Commits

Reviewing files that changed from the base of the PR and between a05f186 and 37a2015.

📒 Files selected for processing (5)
  • downstreamadapter/dispatchermanager/dispatcher_manager.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_redo.go
  • downstreamadapter/dispatchermanager/dispatcher_manager_test.go
  • downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator.go
  • downstreamadapter/dispatcherorchestrator/dispatcher_orchestrator_test.go

@ti-chi-bot ti-chi-bot Bot added the lgtm label Jun 12, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: 3AceShowHand, wk989898

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [3AceShowHand,wk989898]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot removed the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Jun 12, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-06-05 08:26:11.880158741 +0000 UTC m=+516472.950476131: ☑️ agreed by wk989898.
  • 2026-06-12 02:25:19.277915429 +0000 UTC m=+1099620.348232849: ☑️ agreed by 3AceShowHand.

@asddongmen

Copy link
Copy Markdown
Collaborator Author

/hold

@ti-chi-bot ti-chi-bot Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 12, 2026
Signed-off-by: dongmen <414110582@qq.com>
@asddongmen

Copy link
Copy Markdown
Collaborator Author

/test all

@asddongmen asddongmen removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 12, 2026
@ti-chi-bot ti-chi-bot Bot merged commit f73e8db into pingcap:master Jun 12, 2026
24 of 26 checks passed
@3AceShowHand

Copy link
Copy Markdown
Collaborator

/cherry-pick release-nextgen-202603

@ti-chi-bot

Copy link
Copy Markdown
Member

@3AceShowHand: new pull request created to branch release-nextgen-202603: #5600.
But this PR has conflicts, please resolve them!

Details

In response to this:

/cherry-pick release-nextgen-202603

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the ti-community-infra/tichi repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TiCDC node may keep writing after losing its etcd session

4 participants