Skip to content

fix(lockservice): honor context cancellation in lock waits#25901

Merged
XuPeng-SH merged 9 commits into
matrixorigin:mainfrom
aptend:fix/lockservice-context-cancellation-25790-25795
Jul 21, 2026
Merged

fix(lockservice): honor context cancellation in lock waits#25901
XuPeng-SH merged 9 commits into
matrixorigin:mainfrom
aptend:fix/lockservice-context-cancellation-25790-25795

Conversation

@aptend

@aptend aptend commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • API-change
  • BUG
  • Improvement
  • Documentation
  • Feature
  • Test and CI
  • Code Refactoring

Which issue(s) this PR fixes:

Fixes #25790
Fixes #25795

What this PR does / why we need it:

  • propagates caller context through lockservice readiness waits, bind allocation, allocator/RPC calls, remote retries, waiting-list traversal, and deadlock detection
  • rejects cancellation before local lock or bind-publication linearization points while preserving must-complete transaction cleanup semantics
  • makes deadlock detector shutdown cancel in-flight remote GetTxnLock retries
  • serializes lock admission with rolling-restart drain transitions
  • fixes proxy shared-lock cancellation and waiter notify/cancel races so callbacks and pooled waiter cleanup remain exactly once
  • keeps stale-allocator bind fencing intact after bind refreshes

Validation:

  • focused cancellation tests repeated with -count=100
  • focused concurrency tests repeated under -race -count=20
  • pkg/lockservice full test suite
  • pkg/lockservice full test suite with -race
  • go build ./pkg/lockservice ./pkg/cnservice
  • go vet ./pkg/lockservice ./pkg/cnservice
  • TestDefaultCnConfig
  • independent read-only review loop converged to PASS

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@mergify mergify Bot added the kind/bug Something isn't working label Jul 20, 2026
@matrix-meow matrix-meow added the size/XL Denotes a PR that changes [1000, 1999] lines label Jul 20, 2026

@XuPeng-SH XuPeng-SH 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.

Request changes: the cancellation work is mostly sound, but the new admission/drain protocol is not a closed quiescence protocol yet.

  1. pkg/lockservice/service.go:458-480,521-543: checkCanMoveGroupTables waits for lockAdmissions == 0 while the service remains ServiceLockEnable; therefore beginLockAdmission continues admitting new work. Under continuous traffic, or one lock wait without a deadline, the count may never reach zero and rolling restart can starve indefinitely. The drain request must atomically close the admission gate first, then wait only for the already-admitted set.

  2. pkg/lockservice/service_remote.go:278-372 plus pkg/lockservice/txn.go:463-475: remote owner locks are asynchronous. Once a waiter is queued, l.lock(... async:true ...) returns and the handler defer releases admission before the callback. At that point the transaction has only tableBindIntents, while the drain snapshot counts only tableBinds. A concrete ordering is: holder owns table T; remote waiter for T is queued and its admission ends; drain snapshots the holder only; holder Unlock decrements the last ref before releasing/notifying; T is published as movable; the waiter then acquires T, but its already-finished handler can no longer add the ref. The service can therefore migrate a table with a live/pending transaction. The stack-local lockErr is also written by the async callback but may be read by the handler defer when admission began in Waiting state, which is an unsynchronized race.

Please make the linearization explicit and shared by synchronous and asynchronous paths: close/latch the drain gate under s.mu, publish an admitted operation as an active bind intent before releasing admission, and make drain ref accounting plus its decrement symmetric over acquired binds and in-flight intents (or an equivalent design with the same invariants). Do not rely on a transient zero count while the gate is open.

Please add deterministic tests for: (a) a drain request closes admission while an existing Lock is blocked, (b) an async remote waiter survives holder unlock without its table becoming movable, (c) continuous new Lock attempts cannot starve the drain, and (d) the Waiting-state async path under -race.

Validation on current head 1c7910fa2368d3037a294081d61188434f48f2ca: all newly added focused tests passed; full go test -count=1 -timeout=180s ./pkg/lockservice passed in 125.6s; focused race tests, go vet, go build, and git diff --check passed.

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

Codex automated review

Two blocking flaws remain in the new rolling-restart admission/drain protocol. Focused tests could not start because this checkout lacks cgo/libmo.dylib.

P1 - Close the admission gate before waiting for a drain (pkg/lockservice/service.go:461)

checkCanMoveGroupTables returns while lockAdmissions > 0 but leaves the status at ServiceLockEnable; beginLockAdmission therefore continues admitting new Lock calls. A blocked lock with no deadline, or sustained traffic that keeps the count nonzero, prevents the service from ever reaching ServiceLockWaiting/ServiceUnLockSucc, so a rolling restart can starve indefinitely. The drain transition must atomically reject new admissions first, then wait only for the set already admitted.

P1 - Keep asynchronous remote waiters protected by the drain snapshot (pkg/lockservice/service_remote.go:344)

For a conflicting remote-owner lock, localLockTable.doLock queues the waiter and returns immediately in async mode. The handler then releases its admission while the transaction has only tableBindIntents; incLockTableRef snapshots only tableBinds (txn.go:463), so the drain can publish that table as movable before the callback acquires it. The identical deferred accounting also reads lockErr before the later callback writes it (lines 369/471), creating a race and potentially adding a permanent ref for a failed wait. Account the in-flight intent through callback completion, with one synchronized linearization point shared by both handlers and acquisition/failure paths.

@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-07-21 09:06 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • Checks failed · in-place
  • 🚫 Left the queue2026-07-21 11:22 UTC · at 25e4319abe33af7b4a6725e8204fc44fdec9002c

This pull request spent 2 hours 16 minutes 22 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection] (documentation)
  • github-review-decision = APPROVED [🛡 GitHub branch protection] (documentation)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-success = Matrixone CI / SCA Test on Linux/arm64
    • check-neutral = Matrixone CI / SCA Test on Linux/arm64
    • check-skipped = Matrixone CI / SCA Test on Linux/arm64

Reason

The merge conditions cannot be satisfied due to failing checks

Failing checks:

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

Requeued — the merge queue status continues in this comment ↓.

# Conflicts:
#	pkg/lockservice/service.go
#	pkg/lockservice/service_forward.go
#	pkg/lockservice/service_remote.go
#	pkg/lockservice/service_test.go
#	pkg/lockservice/waiter.go
@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-07-21 13:47 UTC · Rule: main · triggered by rule Automatic queue on approval for main
  • 🟠 Checks running · in-place
  • 🚫 Left the queue2026-07-21 15:19 UTC · at 0ca388859b79869caa46a7a4f468b1b5c5461f2a

This pull request spent 1 hour 31 minutes 56 seconds in the queue, with no time running CI.

Waiting for
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • any of: [🛡 GitHub branch protection]
    • check-neutral = Matrixone CI / SCA Test on Linux/arm64
    • check-skipped = Matrixone CI / SCA Test on Linux/arm64
    • check-success = Matrixone CI / SCA Test on Linux/arm64
All conditions
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-skipped = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
    • check-success = Matrixone Compose CI / multi cn e2e bvt test docker compose(PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-skipped = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
    • check-success = Matrixone Standlone CI / Multi-CN e2e BVT Test on Linux/x64(LAUNCH, PROXY)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-skipped = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
    • check-success = Matrixone Standlone CI / e2e BVT Test on Linux/x64(LAUNCH, PESSIMISTIC)
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone CI / UT Test on Ubuntu/x86
    • check-skipped = Matrixone CI / UT Test on Ubuntu/x86
    • check-success = Matrixone CI / UT Test on Ubuntu/x86
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone Utils CI / Coverage
    • check-skipped = Matrixone Utils CI / Coverage
    • check-success = Matrixone Utils CI / Coverage
  • any of [🛡 GitHub branch protection]:
    • check-neutral = Matrixone CI / SCA Test on Linux/arm64
    • check-skipped = Matrixone CI / SCA Test on Linux/arm64
    • check-success = Matrixone CI / SCA Test on Linux/arm64
  • #review-threads-unresolved = 0 [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub branch protection] (documentation)
  • github-review-decision = APPROVED [🛡 GitHub branch protection] (documentation)

Reason

Pull request #25901 has been dequeued

Pull request from fork cannot be queued. This pull request comes from a fork, and Mergify needs the author's permission to update its branch.

The author needs to enable "Allow edits from maintainers" on this pull request.

Failing checks:

Hint

You should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it.
If you do update this pull request, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

Tick the box to put this pull request back in the merge queue (same as @mergifyio queue).

  • Requeue this pull request

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

Labels

dequeued kind/bug Something isn't working size/XL Denotes a PR that changes [1000, 1999] lines

Projects

None yet

4 participants