Skip to content

importinto: make dangling import jobs cancellable - #69701

Merged
ti-chi-bot[bot] merged 2 commits into
pingcap:masterfrom
D3Hunter:codex/cancellable-dangling-import-job
Jul 13, 2026
Merged

ti-chi-bot[bot] merged 2 commits into
pingcap:masterfrom
D3Hunter:codex/cancellable-dangling-import-job

Conversation

@D3Hunter

@D3Hunter D3Hunter commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: ref #61702

Problem Summary:

In nextgen user keyspace mode, IMPORT INTO creates the import job and submits the DXF task in separate transactions. If DXF task submission does not complete, the import job can remain without a matching DXF task, and CANCEL IMPORT JOB reports task not found instead of cancelling the dangling import job.

There is also a race where a user can cancel the import job between import-job creation and DXF task scheduling. The import scheduler must treat this as cancellation and avoid reverting a legitimate later-running task when multiple scheduler owners observe the same import job.

What changed and how does it work?

This PR makes dangling nextgen import jobs cancellable and hardens import-into scheduler ownership races:

  • Falls back to cancelling the mysql.tidb_import_jobs row when CANCEL IMPORT JOB cannot find a matching DXF task by task key.
  • Checks whether the import job was already cancelled before scheduler prepare and before subtask planning, returning the existing cancellation-classified error so the scheduler enters the cancel/revert path.
  • Reads back import-job state after scheduler job/step transitions.
  • Treats duplicate scheduler owners that observe the requested import-job step as idempotent success.
  • Treats later import-job steps advanced by another owner as retryable, avoiding backward phase movement and avoiding accidental revert of a later-running task.
  • Adds regression coverage for dangling cancellation, cancelled scheduler admission, same-step duplicate owner behavior, and stale-owner advanced-step behavior.

Based on the transaction matrix documented in pkg/dxf/importinto/job_doc.go, this PR intentionally leaves the following cases for later PRs:

  • The missed-cancel window where CANCEL IMPORT JOB attempts to cancel before the DXF task transaction commits, but WaitTaskDoneByKey later finds the newly committed task and therefore does not use the dangling-job fallback; that cancel can still wait for the task's natural terminal state.
  • Existing terminal-state races where a cancel observes a DXF task already outside the cancellable-state predicate, or where the import job and DXF task terminal states are written by different owners. This PR does not change the "first terminal import-job writer wins" behavior.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.
./tools/check/failpoint-go-test.sh pkg/dxf/importinto -run 'TestSchedulerExtGlobalSort|TestSubmitTaskNextgen/cancel_dangling_user_keyspace_job_without_DXF_task|TestSchedulerExtLocalSort' -count=1 -tags=intest,deadlock,nextgen

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

Make dangling nextgen IMPORT INTO jobs cancellable when no matching DXF task exists.

Summary by CodeRabbit

  • New Features

    • Improved IMPORT INTO cancellation handling for next-gen distributed tasks, including better support for jobs that are cancelled before all related work is created.
    • Added broader coverage for cancellation scenarios across different import stages.
  • Bug Fixes

    • Cancelled import jobs now stop cleanly even when no matching distributed task exists yet.
    • Scheduler behavior now checks for cancelled jobs earlier, reducing work on jobs that are no longer active.
    • Improved consistency of job status and cancellation results in edge cases.

@ti-chi-bot

ti-chi-bot Bot commented Jul 7, 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 release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds cancellation handling for IMPORT INTO jobs on user keyspaces that lack a corresponding DXF task ("dangling" jobs). It introduces a checkImportJobNotCancelled scheduler check, a cancelDanglingImportJob executor helper, a refactored cancelJobInState cancel SQL path, a new IsCancelled predicate, documentation of the job/task transaction model, and corresponding tests and config updates.

Changes

Dangling import job cancellation

Layer / File(s) Summary
Transaction model documentation and failpoint hook
pkg/dxf/importinto/job_doc.go, pkg/dxf/importinto/BUILD.bazel, pkg/dxf/importinto/job.go
Adds documentation of job/task transaction boundaries and cancel/submit interleavings; registers the doc file in the Bazel target; adds an afterUserImportJobCreatedBeforeDXFTask failpoint hook in doSubmitTask.
Job cancel SQL refactor and IsCancelled predicate
pkg/executor/importer/job.go, pkg/executor/importer/job_test.go, pkg/executor/importer/BUILD.bazel
Adds JobInfo.IsCancelled(); refactors CancelJob/CancelPendingJob to a shared cancelJobInState helper with dynamic status IN clause; adds affected-rows assertions and a new TestCancelPendingJob; bumps test shard count.
Executor-level dangling task cancel and wait logic
pkg/executor/import_into.go, pkg/executor/import_into_test.go, pkg/executor/BUILD.bazel
Reworks cancelAndWaitImportJob to detect ErrTaskNotFound and invoke new cancelDanglingImportJob; adds TestCancelImportJobWithoutDXFTask validating no DXF task creation and job status transitions; adds a Bazel dependency.
Scheduler cancellation checks in OnPrepare/OnNextSubtasksBatch
pkg/dxf/importinto/scheduler.go, pkg/dxf/importinto/scheduler_testkit_test.go, pkg/dxf/importinto/job_testkit_test.go
Adds checkImportJobNotCancelled helper called from OnPrepare and OnNextSubtasksBatch; updates testkit tests for cancel-before-admission and cancel-before/after-prepare scenarios, plus a dangling-job cancel test.
Cross-keyspace end-to-end cancellation test
tests/realtikvtest/importintotest3/cross_ks_test.go, tests/realtikvtest/importintotest3/BUILD.bazel, tests/realtikvtest/configs/next-gen/pd.toml
Adds TestCancelDanglingImportJobOnUserKeyspace with fake GCS server and failpoint-coordinated subtests, supporting helpers, keyspace pre-allocation update, and shard count bump.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • pingcap/tidb#68668: Both PRs modify the same next-gen IMPORT INTO scheduler flow in pkg/dxf/importinto (OnPrepare/prepare-mode transitions and doSubmitTask).

Suggested labels: component/import, approved, lgtm

Suggested reviewers: joechenrh, GMHDBJD, wjhuang2016

Poem

A rabbit hops through keyspace halls,
Chasing tasks that dodge and stall,
"Cancelled!" it cries, no dangling job,
No orphan task left to lob. 🐰
With checks in place and tests galore,
Import jobs cancel clean, forevermore.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: making dangling import jobs cancellable.
Description check ✅ Passed The description matches the template well, includes the issue reference, problem and change summary, test checklist, documentation note, and release note.
✨ 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.

@ti-chi-bot ti-chi-bot Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. component/statistics sig/planner SIG: Planner and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 7, 2026
D3Hunter added 2 commits July 9, 2026 20:31
importinto: simplify dangling job cancellation

importinto: clarify dangling cancel fallback

executor: document import cancel race

change

docs(importinto): document job cancellation races

executor: cancel dangling import jobs

change

change

importinto: test dangling job cancellation

importinto: cover dangling cancel fallback
@D3Hunter
D3Hunter force-pushed the codex/cancellable-dangling-import-job branch from b11c243 to 0e58d40 Compare July 9, 2026 12:33
@D3Hunter
D3Hunter marked this pull request as ready for review July 9, 2026 12:34
@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 Jul 9, 2026

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
pkg/executor/importer/job.go (1)

57-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pre-existing typo: jogStatusCancelled should be jobStatusCancelled.

The constant jogStatusCancelled (line 57) has a typo — "jog" instead of "job". This is pre-existing, but the new IsCancelled method and cancelJobInState helper now add new references to it. Consider renaming to jobStatusCancelled for clarity, though this touches other files that reference the constant.

✏️ Optional rename
-	jogStatusCancelled = "cancelled"
+	jobStatusCancelled = "cancelled"

Then update all references (including lines 142 and 516 in this file, plus any references in other files).

Also applies to: 142-142, 516-516

🤖 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 `@pkg/executor/importer/job.go` at line 57, The constant name has a typo:
`jogStatusCancelled` should be `jobStatusCancelled`, and the new `IsCancelled`
method plus `cancelJobInState` helper should use the corrected name for clarity.
Rename the constant in `job.go` and update all references in the same file, then
sweep any other call sites in the importer package or elsewhere that still refer
to `jogStatusCancelled` so everything compiles consistently.
🤖 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 `@pkg/executor/importer/job.go`:
- Line 57: The constant name has a typo: `jogStatusCancelled` should be
`jobStatusCancelled`, and the new `IsCancelled` method plus `cancelJobInState`
helper should use the corrected name for clarity. Rename the constant in
`job.go` and update all references in the same file, then sweep any other call
sites in the importer package or elsewhere that still refer to
`jogStatusCancelled` so everything compiles consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: de79b77f-3fe8-46e2-8c82-0e6a9584bcbf

📥 Commits

Reviewing files that changed from the base of the PR and between ab7d93b and 0e58d40.

📒 Files selected for processing (15)
  • pkg/dxf/importinto/BUILD.bazel
  • pkg/dxf/importinto/job.go
  • pkg/dxf/importinto/job_doc.go
  • pkg/dxf/importinto/job_testkit_test.go
  • pkg/dxf/importinto/scheduler.go
  • pkg/dxf/importinto/scheduler_testkit_test.go
  • pkg/executor/BUILD.bazel
  • pkg/executor/import_into.go
  • pkg/executor/import_into_test.go
  • pkg/executor/importer/BUILD.bazel
  • pkg/executor/importer/job.go
  • pkg/executor/importer/job_test.go
  • tests/realtikvtest/configs/next-gen/pd.toml
  • tests/realtikvtest/importintotest3/BUILD.bazel
  • tests/realtikvtest/importintotest3/cross_ks_test.go

@codecov

codecov Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 87 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.2673%. Comparing base (ab7d93b) to head (0e58d40).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #69701        +/-   ##
================================================
- Coverage   76.3201%   74.2673%   -2.0529%     
================================================
  Files          2041       2054        +13     
  Lines        560169     581377     +21208     
================================================
+ Hits         427522     431773      +4251     
- Misses       131746     148867     +17121     
+ Partials        901        737       -164     
Flag Coverage Δ
integration 41.6471% <0.0000%> (+1.9418%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 60.4471% <ø> (ø)
parser ∅ <ø> (∅)
br 47.4060% <ø> (-15.3154%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ingress-bot

Copy link
Copy Markdown

🔍 Starting code review for this PR...

@ingress-bot ingress-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.

This review was generated by AI and should be verified by a human reviewer.
Manual follow-up is recommended before merge.

Summary

  • Total findings: 7
  • Inline comments: 2
  • Summary-only findings (no inline anchor): 5
Findings (highest risk first)

🟡 [Minor] (7)

  1. "cancellable" job-status set is hardcoded independently in three places, and this PR adds a fourth variant (pkg/executor/importer/job.go:136, pkg/executor/importer/job.go:198, pkg/executor/importer/job.go:494, pkg/executor/importer/job.go:499)
  2. CancelJob doc comment still references a non-existent "paused" job status (pkg/executor/importer/job.go:492)
  3. New TODO for the highest-priority cancel race has no owner or tracking issue (pkg/executor/import_into.go:417)
  4. New job-cancelled error text is an undocumented string contract with scheduler.IsCancelledErr (pkg/dxf/importinto/scheduler.go:839, pkg/dxf/framework/scheduler/scheduler.go:812)
  5. cancelJobInState over-generalizes a two-caller helper into a dynamic-arity SQL builder (pkg/executor/importer/job.go:494, pkg/executor/importer/job.go:499, pkg/executor/importer/job.go:503)
  6. job_doc.go's design notes are a dangling comment, not attached package documentation (pkg/dxf/importinto/job_doc.go:15, pkg/dxf/importinto/job_doc.go:17)
  7. Dangling-cancel leaves no durable marker; an old/rolled-back scheduler diverges from the new revert path (pkg/executor/import_into.go:434, pkg/executor/import_into.go:448, pkg/dxf/importinto/scheduler.go:805)

Unanchored findings

🟡 [Minor] (5)

  1. "cancellable" job-status set is hardcoded independently in three places, and this PR adds a fourth variant
    • Request: Introduce one shared []string (or small named set) for the "active"/"cancellable" statuses and have CanCancel, GetActiveJobCnt, and the CancelJob/CancelPendingJob call sites reference it, so a future status change only needs one edit.
  2. CancelJob doc comment still references a non-existent "paused" job status
    • Request: Update the doc comment on CancelJob to say "Only a pending/running job can be canceled" to match the states actually passed to cancelJobInState.
  3. New TODO for the highest-priority cancel race has no owner or tracking issue
    • Request: Attach a tracking issue (or owner) to this TODO so the documented "Highest" risk from job_doc.go isn't only discoverable by reading source comments.
  4. New job-cancelled error text is an undocumented string contract with scheduler.IsCancelledErr
    • Request: Add a short comment at the errors.Errorf call (and/or at IsCancelledErr) stating that the message text is matched by scheduler.IsCancelledErr and must retain the taskCancelMsg substring, or replace the substring match with a typed/sentinel error so the coupling is enforced by the compiler instead of prose.
  5. cancelJobInState over-generalizes a two-caller helper into a dynamic-arity SQL builder
    • Request: Replace the variadic SQL-builder with two simple, direct UPDATE statements (as CancelJob had before), or at minimum drop the runtime placeholder-count loop and keep a fixed two-arg form if a shared helper is still wanted.

// See the License for the specific language governing permissions and
// limitations under the License.

package importinto

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 [Minor] job_doc.go's design notes are a dangling comment, not attached package documentation

Impact
The new file's entire content is a single large comment block meant to document the cancel/race invariants for future maintainers ("intentionally kept close to the importinto code").
Because a blank line separates it from package importinto and no declaration follows it, Go's doc-comment attachment rules do not treat it as the package doc: go doc ./pkg/dxf/importinto, pkg.go.dev, and IDE package-overview popups will not surface this content at all.
The existing sibling doc-only files in this codebase (pkg/dxf/example/doc.go, pkg/dxf/framework/doc.go, pkg/dxf/importinto/conflictedkv/doc.go) follow the immediately-attached, no-blank-line convention that this file breaks.

Scope

  • pkg/dxf/importinto/job_doc.go:15 — package importinto
  • pkg/dxf/importinto/job_doc.go:17

Evidence
In job_doc.go, line 15 is package importinto, line 16 is blank, and line 17 starts the comment // IMPORT INTO job and DXF task transaction notes.; there is no declaration anywhere in the file for the comment to attach to, so it never becomes discoverable package documentation.

Change request
Remove the blank line so the comment sits directly above package importinto (and consider starting it with // Package importinto ... per Go doc convention), or rename the file to doc.go to match this package family's existing doc-file convention.

zap.Int64("jobID", jobID),
zap.String("taskKey", taskKey))
failpoint.InjectCall("beforeCancelDanglingImportJob", jobID)
return cancelDanglingImportJob(ctx, jobID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 [Minor] Dangling-cancel leaves no durable marker; an old/rolled-back scheduler diverges from the new revert path

Impact
The dangling cancel only flips tidb_import_jobs to cancelled and writes nothing durable into the DXF task table; the sole safeguard is the new checkImportJobNotCancelled hook.
During a nextgen rolling upgrade (or after a downgrade), an old SYSTEM-keyspace scheduler owning a task committed after the cancel lacks that hook, so it runs the import to completion: the user is told cancel succeeded and the job row shows cancelled, but the data is fully imported and the DXF task reaches succeed instead of reverted.

Scope

  • pkg/executor/import_into.go:434 — cancelDanglingImportJob
  • pkg/executor/import_into.go:448 — CancelPendingJob
  • pkg/dxf/importinto/scheduler.go:805 — checkImportJobNotCancelled

Evidence
cancelDanglingImportJob calls CancelPendingJob (job row only) and records no cancel state on the later-committed DXF task. Revert then depends entirely on checkImportJobNotCancelled, added in this PR to OnPrepare/OnNextSubtasksBatch, whose cancel signal is a plain errors.Errorf("import job %d cancelled by user") matched only by the "cancelled by user" substring in IsCancelledErr. An old scheduler binary has neither the hook nor that check, so it neither reverts nor no-ops. The only tests are all-new-cluster (TestCancelDanglingImportJobOnUserKeyspace, TestSchedulerExtLocalSort) and assert TaskStateReverted/0 rows — behavior an old scheduler would not reproduce; no mixed-version or downgrade case is exercised.

Change request
Either version-gate/feature-gate the executor's direct dangling-cancel (or its user-visible success) on the scheduler-side check being present, or record a durable cancel intent that any scheduler version honors. If lockstep-upgrade is the intended contract, state the required upgrade ordering and downgrade window in the PR/release notes and note the mixed-version window is untested.

@D3Hunter

D3Hunter commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/retest

2 similar comments
@D3Hunter

D3Hunter commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@D3Hunter

D3Hunter commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Jul 10, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: joechenrh, wjhuang2016

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:

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 Jul 13, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-07-10 10:47:31.80087413 +0000 UTC m=+365037.836969187: ☑️ agreed by wjhuang2016.
  • 2026-07-13 02:58:22.305252529 +0000 UTC m=+596088.341347575: ☑️ agreed by joechenrh.

@D3Hunter

Copy link
Copy Markdown
Contributor Author

/retest

@D3Hunter

Copy link
Copy Markdown
Contributor Author

/cherry-pick release-nextgen-202603

@ti-chi-bot

Copy link
Copy Markdown
Member

@D3Hunter: once the present PR merges, I will cherry-pick it on top of release-nextgen-202603 in the new PR and assign it to you.

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.

@ti-chi-bot
ti-chi-bot Bot merged commit 59f6e85 into pingcap:master Jul 13, 2026
37 of 39 checks passed
@ti-chi-bot

Copy link
Copy Markdown
Member

@D3Hunter: new pull request created to branch release-nextgen-202603: #69787.
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 component/statistics lgtm release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner 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.

5 participants