Skip to content

request: treat an undetermined apply outcome as terminal at commit points - #555

Open
eduralph wants to merge 1 commit into
tikv:masterfrom
getwyrd:undetermined-result
Open

request: treat an undetermined apply outcome as terminal at commit points#555
eduralph wants to merge 1 commit into
tikv:masterfrom
getwyrd:undetermined-result

Conversation

@eduralph

@eduralph eduralph commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Stacked on #550 — please review the second commit only. Until #550 merges, the diff below also
contains its re-vendor.

Why: the re-vendored kvproto exposes errorpb.UndeterminedResult — the server telling us the
raft apply outcome is UNKNOWN. Today it falls through to the generic region-error arm and is retried
like any transient failure. For most requests that's right; for a commit point it isn't. "Failed"
must never be claimed for a transaction that may already be durable.

What: follow client-go's shape. Its transport never retries UndeterminedResult ("should not
retry … processed by the caller", region_request.go) and each action decides. So the plan layer
keeps retry as the default — replaying an idempotent request resolves the uncertainty, and on
backoff exhaustion the error escapes unchanged so callers can still classify it — while plans for
which a replay is unsafe opt in to terminal-on-first-sight:

  • raw CAS — a replay would compare against its own effect and report succeed = false for a
    write that happened.
  • the primary commit — client-go returns ErrResultUndetermined here (commit.go) rather than
    retrying.
  • async-commit / 1PC prewrites — the prewrite is the commit point. Stricter than client-go, in
    the safe direction.

Shard aggregation is fixed alongside: collect::<Result<_>>() returns the first error by shard
index
, so a lower-index shard's determinate error could mask a higher-index shard's
UndeterminedResult and tell the caller the request definitely failed. An undetermined error from
any shard now wins.

Commit paths classify the surfaced error as UndeterminedError. Two places are deliberately
conservative and say so in comments: a 1PC prewrite that sharding downgraded to 2PC, and an
undetermined error arriving from the commit plan's resolve_lock layer rather than the commit
dispatch. Both only over-report uncertainty, which makes the caller verify instead of replay.

Verification: 4 new tests — terminal for opted-in plans under a generous backoff (proving
terminal, not merely retry-exhausted), preserved unchanged on retry exhaustion, recognizer
positive/negative, and the shard-masking hazard. 71 lib tests; make check green; txn/raw/failpoint
integration suites green against a local api-v2 cluster.

Summary by CodeRabbit

  • Bug Fixes

    • Improved transaction commit handling when the final outcome cannot be determined.
    • Prevented unsafe retries for operations that may already have been applied.
    • Preserved and surfaced undetermined-region errors instead of masking them with less-specific failures.
    • Improved error prioritization when multiple regions report failures.
  • New Features

    • Added an option for multi-region operations to stop immediately when results are undetermined, providing clearer and more accurate transaction outcomes.

@ti-chi-bot ti-chi-bot Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. dco-signoff: yes Indicates the PR's author has signed the dco. labels Jul 25, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign yujuncen for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found 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

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e62601fa-a9de-4fd5-b701-7aa02ab9a838

📥 Commits

Reviewing files that changed from the base of the PR and between 4f0c54c and 9aebc0e.

📒 Files selected for processing (4)
  • src/raw/client.rs
  • src/request/plan.rs
  • src/request/plan_builder.rs
  • src/transaction/transaction.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/raw/client.rs
  • src/request/plan_builder.rs
  • src/transaction/transaction.rs
  • src/request/plan.rs

📝 Walkthrough

Walkthrough

The change adds configurable terminal handling for undetermined multi-region results. CAS and selected transaction commit paths stop retrying these results. Retry aggregation preserves undetermined errors, and transaction code converts qualifying failures to Error::UndeterminedError.

Changes

Undetermined retry semantics

Layer / File(s) Summary
Undetermined multi-region retry handling
src/request/plan.rs
Detects undetermined_result, propagates terminal handling through shard and retry execution, preserves undetermined errors, and prioritizes them during shard aggregation. Tests cover terminal behavior, recognition, retry preservation, and error masking.
Terminal retry plan construction
src/request/plan_builder.rs, src/raw/client.rs
Adds retry_multi_region_terminal_on_undetermined. compare_and_swap uses this retry mode. Existing retry methods retain non-terminal behavior.
Transaction undetermined outcome propagation
src/transaction/transaction.rs
Uses terminal retries for async commit, 1PC, and primary commit paths. Converts qualifying prewrite and primary commit failures into transaction-level undetermined errors.

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

Suggested labels: lgtm, approved

Suggested reviewers: iosmanthus, pingyu

Sequence Diagram(s)

sequenceDiagram
  participant Committer
  participant RetryableMultiRegion
  participant Region
  Committer->>RetryableMultiRegion: prewrite or commit_primary with terminal retry
  RetryableMultiRegion->>Region: execute request
  Region-->>RetryableMultiRegion: undetermined_result region error
  RetryableMultiRegion-->>Committer: return without replay
  Committer-->>Committer: convert failure to Error::UndeterminedError
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: treating undetermined apply outcomes as terminal at commit points.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 contribution This PR is from a community contributor. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Jul 25, 2026
@eduralph
eduralph force-pushed the undetermined-result branch 2 times, most recently from eddb093 to 4f0c54c Compare July 25, 2026 22:03
@eduralph
eduralph marked this pull request as ready for review July 25, 2026 22:12
@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 25, 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)
proto/encryptionpb.proto (1)

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

Buf compile errors here are an include-path artifact, not a file defect.

gogoproto/gogo.proto and rustproto.proto live under proto/include/; Buf resolves imports relative to its module root, so every vendored file reports the same unresolvable-option/import errors. Consider adding a buf.yaml/buf.work.yaml that registers proto and proto/include as roots (or excluding vendored protos from Buf) so CI signal isn't drowned in false positives.

🤖 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 `@proto/encryptionpb.proto` around lines 12 - 18, Update the Buf configuration
rather than proto/encryptionpb.proto: register both proto and proto/include as
module roots so imports of gogoproto/gogo.proto and rustproto.proto resolve, or
exclude the vendored protos from Buf validation. Ensure CI no longer reports
false-positive compile errors for these vendored files.

Source: Linters/SAST tools

🤖 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 `@proto/encryptionpb.proto`:
- Around line 12-18: Update the Buf configuration rather than
proto/encryptionpb.proto: register both proto and proto/include as module roots
so imports of gogoproto/gogo.proto and rustproto.proto resolve, or exclude the
vendored protos from Buf validation. Ensure CI no longer reports false-positive
compile errors for these vendored files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff4ddb26-826e-403b-b41f-609a275ef6b7

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0f2f3 and 4f0c54c.

⛔ Files ignored due to path filters (21)
  • src/generated/backup.rs is excluded by !**/generated/**
  • src/generated/cdcpb.rs is excluded by !**/generated/**
  • src/generated/coprocessor.rs is excluded by !**/generated/**
  • src/generated/deadlock.rs is excluded by !**/generated/**
  • src/generated/encryptionpb.rs is excluded by !**/generated/**
  • src/generated/errorpb.rs is excluded by !**/generated/**
  • src/generated/import_sstpb.rs is excluded by !**/generated/**
  • src/generated/keyspacepb.rs is excluded by !**/generated/**
  • src/generated/kvrpcpb.rs is excluded by !**/generated/**
  • src/generated/logbackup.rs is excluded by !**/generated/**
  • src/generated/meta_storagepb.rs is excluded by !**/generated/**
  • src/generated/metapb.rs is excluded by !**/generated/**
  • src/generated/mpp.rs is excluded by !**/generated/**
  • src/generated/pdpb.rs is excluded by !**/generated/**
  • src/generated/raft_cmdpb.rs is excluded by !**/generated/**
  • src/generated/raft_serverpb.rs is excluded by !**/generated/**
  • src/generated/resource_manager.rs is excluded by !**/generated/**
  • src/generated/resource_usage_agent.rs is excluded by !**/generated/**
  • src/generated/schedulingpb.rs is excluded by !**/generated/**
  • src/generated/tikvpb.rs is excluded by !**/generated/**
  • src/generated/tsopb.rs is excluded by !**/generated/**
📒 Files selected for processing (42)
  • proto/VERSION
  • proto/autoid.proto
  • proto/brpb.proto
  • proto/cdcpb.proto
  • proto/configpb.proto
  • proto/coprocessor.proto
  • proto/deadlock.proto
  • proto/debugpb.proto
  • proto/diagnosticspb.proto
  • proto/disaggregated.proto
  • proto/disk_usage.proto
  • proto/encryptionpb.proto
  • proto/enginepb.proto
  • proto/errorpb.proto
  • proto/gcpb.proto
  • proto/import_kvpb.proto
  • proto/import_sstpb.proto
  • proto/include/eraftpb.proto
  • proto/include/gogoproto/gogo.proto
  • proto/include/rustproto.proto
  • proto/keyspacepb.proto
  • proto/kvrpcpb.proto
  • proto/logbackuppb.proto
  • proto/meta_storagepb.proto
  • proto/metapb.proto
  • proto/mpp.proto
  • proto/pdpb.proto
  • proto/raft_cmdpb.proto
  • proto/raft_serverpb.proto
  • proto/recoverdatapb.proto
  • proto/replication_modepb.proto
  • proto/resource_manager.proto
  • proto/resource_usage_agent.proto
  • proto/schedulingpb.proto
  • proto/tikvpb.proto
  • proto/tracepb.proto
  • proto/tsopb.proto
  • src/pd/timestamp.rs
  • src/raw/client.rs
  • src/request/plan.rs
  • src/request/plan_builder.rs
  • src/transaction/transaction.rs

@ti-chi-bot ti-chi-bot Bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Jul 26, 2026

@pingyu pingyu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rest LGTM.

Comment thread src/request/plan.rs
…ints

The re-vendored kvproto exposes errorpb.UndeterminedResult: the server telling
us the raft apply outcome is UNKNOWN. Today it falls through to the generic
region-error arm and is retried like any transient failure. For most requests
that is right, but not for a commit point, where "failed" must never be claimed
for a transaction that may already be durable.

Follow client-go's shape: its transport never retries UndeterminedResult
("should not retry ... processed by the caller", region_request.go) and each
ACTION decides. So the plan layer keeps RETRY as the default — replaying an
idempotent request resolves the uncertainty, and on backoff exhaustion the
error escapes UNCHANGED so callers can still classify it — while plans for
which a replay is unsafe opt in to terminal-on-first-sight via
retry_multi_region_terminal_on_undetermined:

- raw CAS: a replay would compare against its OWN effect and report
  succeed = false for a write that happened.
- the primary commit: client-go returns ErrResultUndetermined here (commit.go)
  rather than retrying.
- async-commit / 1PC prewrites: the prewrite IS the commit point. Stricter
  than client-go, in the safe direction.

Shard aggregation is fixed alongside: collect::<Result<_>>() returns the first
error BY SHARD INDEX, so a lower-index shard's determinate error could mask a
higher-index shard's UndeterminedResult and tell the caller the request
definitely failed. An undetermined error from any shard now wins.

Commit paths classify the surfaced error as UndeterminedError. Two places are
deliberately conservative and say so in comments: a 1PC prewrite that sharding
downgraded to 2PC, and an undetermined error arriving from the commit plan's
resolve_lock layer rather than the commit dispatch. Both only over-report
uncertainty, which makes the caller verify instead of replay.

Split out of the kvproto re-vendor at pingyu's suggestion (tikv#550).

Tests: 4 new — terminal for opted-in plans under a generous backoff (proving
terminal, not retry-exhausted), preserved unchanged on retry exhaustion,
recognizer positive/negative, and the shard-masking hazard. 71 lib tests green.

Signed-off-by: Eduard R. <eduard@ralphovi.net>
@eduralph
eduralph force-pushed the undetermined-result branch from 06baed8 to 9aebc0e Compare August 2, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution This PR is from a community contributor. dco-signoff: yes Indicates the PR's author has signed the dco. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants