Skip to content

keyspace: guard meta service group updates#10907

Closed
lhy1024 wants to merge 1 commit into
tikv:masterfrom
lhy1024:fix-meta-service-group-assignment-race
Closed

keyspace: guard meta service group updates#10907
lhy1024 wants to merge 1 commit into
tikv:masterfrom
lhy1024:fix-meta-service-group-assignment-race

Conversation

@lhy1024

@lhy1024 lhy1024 commented Jun 16, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: close #10906

What is changed and how does it work?

  • Reject dynamic meta-service group config updates that remove groups with assigned keyspaces.
  • Serialize manual keyspace meta_service_group_id assignment updates with meta-service group set updates.
  • Keep persisted keyspace config and the meta-service group manager state updated under the same manager lock.

Check List

Tests

  • Unit test
make gotest GOTEST_ARGS='-tags without_dashboard ./pkg/keyspace -run TestMetaServiceGroupTestSuite/TestUpdateGroupsSafely -count=1'
make gotest GOTEST_ARGS='-tags without_dashboard ./pkg/keyspace -run TestKeyspaceTestSuite/TestUpdateKeyspaceConfig -count=1'
make gotest GOTEST_ARGS='-tags without_dashboard ./tests/server/apiv2/handlers -run TestMetaServiceGroupTestSuite/TestSetKeyspaceConfigRejectsAssignedGroupRemoval -count=1'

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability when updating keyspace settings by ensuring related locks are held until background assignment updates finish.
    • Fixed a concurrency issue that could allow configuration updates and group locking to overlap incorrectly.
  • Tests

    • Added a regression test covering the locking behavior during keyspace configuration updates.

@ti-chi-bot

ti-chi-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

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 kubernetes-sigs/prow repository.

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-triage-completed dco-signoff: yes Indicates the PR's author has signed the dco. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Jun 16, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

[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 hundundm 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

@ti-chi-bot ti-chi-bot Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jun 16, 2026
@coderabbitai

coderabbitai Bot commented Jun 16, 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

Run ID: a33c6d14-40a1-4167-814d-afe4bb68757f

📥 Commits

Reviewing files that changed from the base of the PR and between 6530f0b and ca23e87.

📒 Files selected for processing (1)
  • pkg/keyspace/keyspace_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/keyspace/keyspace_test.go

📝 Walkthrough

Walkthrough

Adds a test-only assignmentHookStorage wrapper that injects a callback before IncrementAssignmentCount delegates to the underlying storage, and a new concurrency regression test verifying that MetaServiceGroupManager lock acquisition is blocked until an in-flight UpdateKeyspaceConfig assignment increment completes.

Changes

UpdateKeyspaceConfig lock-ordering regression test

Layer / File(s) Summary
assignmentHookStorage wrapper and concurrency test
pkg/keyspace/keyspace_test.go
Adds assignmentHookStorage wrapping endpoint.StorageEndpoint with an optional beforeIncrementAssignment hook invoked before delegating IncrementAssignmentCount, and adds TestUpdateKeyspaceConfigHoldsMetaServiceGroupReadLockUntilAssignmentTxnDone which pauses the assignment increment, asserts mgm.Lock() is blocked, then releases the pause and asserts lock acquisition and successful config update.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant UpdateKeyspaceConfig
  participant assignmentHookStorage
  participant MetaServiceGroupManager

  Test->>assignmentHookStorage: set beforeIncrementAssignment (pause)
  Test->>UpdateKeyspaceConfig: call (goroutine)
  UpdateKeyspaceConfig->>assignmentHookStorage: IncrementAssignmentCount
  assignmentHookStorage->>Test: block on callback
  Test->>MetaServiceGroupManager: attempt Lock()
  MetaServiceGroupManager-->>Test: lock not acquired (timeout)
  Test->>assignmentHookStorage: release pause
  assignmentHookStorage-->>UpdateKeyspaceConfig: IncrementAssignmentCount returns
  UpdateKeyspaceConfig-->>Test: success
  MetaServiceGroupManager-->>Test: lock acquired
Loading

Related issues: #10906 (keyspace: prevent deleting assigned meta-service groups) — this PR adds a regression test relevant to serializing assignment updates with meta-service group changes.

Suggested labels: test, keyspace

Suggested reviewers: rleungx, binshi-bing

🐰 A hook pauses the count mid-flight,
a lock waits patient through the night,
then release — the goroutines race,
and both assertions find their place.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning For [#10906], the changes only add a regression test and do not show the requested production fixes for rejecting deletions or serializing updates. Implement the keyspace/meta-service-group locking and rejection logic from #10906, then add tests that cover those behaviors end to end.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: guarding meta service group updates.
Description check ✅ Passed The PR description follows the template with an issue number, change summary, and test checklist.
Out of Scope Changes check ✅ Passed The changes stay focused on the keyspace/meta-service-group race and related regression coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@lhy1024
lhy1024 force-pushed the fix-meta-service-group-assignment-race branch from 50169ae to 243d766 Compare June 16, 2026 16:42

@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/keyspace/keyspace.go (1)

813-885: 💤 Low value

Consider using defer for RUnlock to guard against panics.

While storage operations typically return errors rather than panic, the current pattern leaves RUnlock unguarded if an unexpected panic occurs within RunInTxn. A defer-based approach would be more resilient.

♻️ Suggested refactor using an anonymous function
-	if manager.mgm != nil {
-		manager.mgm.RLock()
-	}
-	err := manager.store.RunInTxn(manager.ctx, func(txn kv.Txn) error {
+	err := func() error {
+		if manager.mgm != nil {
+			manager.mgm.RLock()
+			defer manager.mgm.RUnlock()
+		}
+		return manager.store.RunInTxn(manager.ctx, func(txn kv.Txn) error {
 		// ... transaction body unchanged ...
-	})
-	if manager.mgm != nil {
-		manager.mgm.RUnlock()
-	}
+		})
+	}()
🤖 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/keyspace/keyspace.go` around lines 813 - 885, The current code calls
manager.mgm.RUnlock() directly after manager.store.RunInTxn() completes, which
leaves it unguarded if a panic occurs during the transaction. Wrap the entire
transaction block (starting with the manager.mgm.RLock() call) in an anonymous
function and defer the manager.mgm.RUnlock() call at the beginning of that
function. This ensures that the unlock happens even if a panic occurs within
RunInTxn, making the code more resilient.
🤖 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/keyspace/keyspace.go`:
- Around line 813-885: The current code calls manager.mgm.RUnlock() directly
after manager.store.RunInTxn() completes, which leaves it unguarded if a panic
occurs during the transaction. Wrap the entire transaction block (starting with
the manager.mgm.RLock() call) in an anonymous function and defer the
manager.mgm.RUnlock() call at the beginning of that function. This ensures that
the unlock happens even if a panic occurs within RunInTxn, making the code more
resilient.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa90f242-0e32-4ab5-8860-cff07f0a20b2

📥 Commits

Reviewing files that changed from the base of the PR and between 50169ae and 243d766.

📒 Files selected for processing (5)
  • pkg/keyspace/keyspace.go
  • pkg/keyspace/meta_service_group.go
  • pkg/keyspace/meta_service_group_test.go
  • server/server.go
  • tests/server/apiv2/handlers/meta_service_group_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/server/apiv2/handlers/meta_service_group_test.go
  • server/server.go

@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/keyspace/keyspace.go (1)

804-806: 💤 Low value

Consider using defer for panic safety.

The current lock/unlock pattern correctly releases the lock after RunInTxn returns (with or without error). However, if RunInTxn panics, the RUnlock would not be called. While panics are rare, a defer pattern would provide additional safety:

if manager.mgm != nil {
    manager.mgm.RLock()
    defer manager.mgm.RUnlock()
}

The trade-off is that defer would hold the lock slightly longer (through the AttachEndpoints call and logging). Given that AttachEndpoints is a quick in-memory operation and the current pattern works correctly for normal execution, the current approach is acceptable if the team prefers minimizing lock hold time.

Also applies to: 874-876

🤖 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/keyspace/keyspace.go` around lines 804 - 806, The lock/unlock pattern for
manager.mgm requires panic safety to ensure the RUnlock is always called. Modify
the code where manager.mgm is locked with RLock to immediately follow with a
defer statement that calls RUnlock on the same manager.mgm object, ensuring the
lock is released even if a panic occurs during the subsequent operations like
RunInTxn or AttachEndpoints. Apply this defer pattern consistently wherever the
manager.mgm RLock pattern is used.

Source: Coding guidelines

🤖 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/keyspace/keyspace.go`:
- Around line 804-806: The lock/unlock pattern for manager.mgm requires panic
safety to ensure the RUnlock is always called. Modify the code where manager.mgm
is locked with RLock to immediately follow with a defer statement that calls
RUnlock on the same manager.mgm object, ensuring the lock is released even if a
panic occurs during the subsequent operations like RunInTxn or AttachEndpoints.
Apply this defer pattern consistently wherever the manager.mgm RLock pattern is
used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a874ca33-898f-4b6f-aa32-3306ca7820d3

📥 Commits

Reviewing files that changed from the base of the PR and between 243d766 and 6530f0b.

📒 Files selected for processing (3)
  • pkg/keyspace/keyspace.go
  • pkg/keyspace/keyspace_test.go
  • pkg/keyspace/meta_service_group.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/keyspace/meta_service_group.go

Comment thread pkg/keyspace/keyspace.go
var meta *keyspacepb.KeyspaceMeta
oldConfig := make(map[string]string)
if manager.mgm != nil {
manager.mgm.RLock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This lock does not cover dynamic meta-service group config updates, so assigned groups can still be deleted.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved after rebasing the PR on the latest master.

The current implementation no longer uses the old narrow lock scope from this diff. updateKeyspaceConfigTxn now calls runTxnWithMetaGroupLock, which holds mgm.RLock() around the whole store.RunInTxn(...), and reassignKeyspaceLocked performs both the group existence validation and the assignment counter update inside that transaction. Dynamic meta-service group updates go through MetaServiceGroupManager.UpdateGroupsSafely, which takes the write lock before deleting groups, so it cannot delete a group while this reassignment transaction is in progress.

I also kept this PR as a regression test only: TestUpdateKeyspaceConfigHoldsMetaServiceGroupReadLockUntilAssignmentTxnDone pauses during IncrementAssignmentCount and verifies a meta-service group writer cannot acquire the write lock until the assignment transaction is released.

Signed-off-by: lhy1024 <19542290+lhy1024@users.noreply.github.com>
@lhy1024
lhy1024 force-pushed the fix-meta-service-group-assignment-race branch from 6530f0b to ca23e87 Compare July 2, 2026 04:10
@ti-chi-bot

ti-chi-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@lhy1024: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-unit-test-next-gen-3 ca23e87 link true /test pull-unit-test-next-gen-3
pull-unit-test-next-gen-2 ca23e87 link true /test pull-unit-test-next-gen-2
pull-integration-realcluster-test ca23e87 link true /test pull-integration-realcluster-test

Full PR test history. Your PR dashboard.

Details

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 kubernetes-sigs/prow repository. I understand the commands that are listed here.

@lhy1024 lhy1024 closed this Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the dco. do-not-merge/needs-triage-completed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

keyspace: prevent deleting assigned meta-service groups

2 participants