Skip to content

server: return unavailable when PD is not serving - #11066

Open
rleungx wants to merge 3 commits into
tikv:masterfrom
rleungx:codex/server-not-serving-unavailable
Open

server: return unavailable when PD is not serving#11066
rleungx wants to merge 3 commits into
tikv:masterfrom
rleungx:codex/server-not-serving-unavailable

Conversation

@rleungx

@rleungx rleungx commented Jul 30, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: ref #11020

GetMembers and GetClusterInfo returned a successful gRPC response with an
ErrorType_UNKNOWN header when the local PD server was not running. During a
graceful shutdown, this made a transient service-availability condition look
like an unclassified application error while the gRPC connection was still
READY.

What is changed and how does it work?

Return the existing gRPC Unavailable error for these service-discovery RPCs
when the local PD server is not running. Also convert ErrServerNotStarted
returned when member loading races with shutdown to the same gRPC status.

Other member-loading failures remain response-header errors, so unrelated
server-side failures are not reclassified as availability failures. GetMembers
continues to apply rate limiting before checking the running state; admitted
requests report Unavailable during shutdown.

This does not change kvproto. The affected RPCs are read-only and clients can
safely retry them against another PD endpoint.

Return gRPC Unavailable from service-discovery RPCs when the local PD
server is not running. Preserve response-header errors for unrelated
member-loading failures.

Check List

Tests

  • Unit test
  • Manual test
    • make gotest GOTEST_ARGS="./server -run Test\(ServiceDiscoveryRPCsReturnUnavailableWhenServerIsNotRunning\|GetMembersErrorResult\) -count=1"
    • make gotest GOTEST_ARGS="./server -count=1"
    • .tools/bin/golangci-lint run ./server --allow-parallel-runners
    • make pd-server-basic
    • On 10.2.8.101, an identical shutdown probe against base
      fad3ff991802633a8de432ad1aa9e2ef2940f824 observed
      response UNKNOWN / READY before the transport closed.
    • The same probe against this change observed rpc Unavailable / READY, then
      rpc Unavailable / TRANSIENT_FAILURE, with no response-header UNKNOWN.

Release note

Fix PD service-discovery RPCs to return gRPC `Unavailable` instead of a response-header `UNKNOWN` error when PD is not serving.

Summary by CodeRabbit

  • Bug Fixes
    • Service discovery gRPC calls now return a consistent “Unavailable” status when the service is closed; closed-state handling is prioritized correctly (e.g., rate-limit failures take precedence).
    • TSO gRPC unary RPCs (FindGroupByKeyspaceID, GetMinTS) now immediately return “Unavailable” when the service is closed.
  • Tests
    • Added/updated in-memory gRPC tests to verify “Unavailable” responses for closed-server scenarios and validate error-mapping behavior.

Return gRPC Unavailable from service-discovery RPCs when the local PD
server is not running. Preserve response-header errors for unrelated
member-loading failures.

Signed-off-by: Ryan Leung <rleungx@gmail.com>
@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. dco-signoff: yes Indicates the PR's author has signed the dco. labels Jul 30, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 30, 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 zhouqiang-cl 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/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Service discovery RPCs now return ErrNotStarted as an unavailable RPC error when PD or TSO services are closed. Member lookup failures use centralized formatting, with bufconn tests covering statuses, nil responses, and preserved generic errors.

Changes

Service discovery RPC errors

Layer / File(s) Summary
PD RPC error mapping
server/grpc_service.go
GetClusterInfo and GetMembers return unavailable RPC errors when the server is closed, while getMembersErrorResult preserves generic failures in response headers.
TSO RPC error mapping
pkg/mcs/tso/server/grpc_service.go
FindGroupByKeyspaceID and GetMinTS check closure before validation, and validRequest separately handles an absent keyspace group manager.
Error handling tests
server/grpc_service_test.go, pkg/mcs/tso/server/grpc_service_test.go
Bufconn tests verify unavailable statuses and nil responses; helper tests verify startup and generic error mappings.

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

Possibly related PRs

  • tikv/pd#10455: Both changes modify FindGroupByKeyspaceID control flow in pkg/mcs/tso/server/grpc_service.go.
  • tikv/pd#10913: Both changes modify GetMembers logic in server/grpc_service.go.

Suggested labels: ok-to-test, lgtm, approved

Suggested reviewers: jmpotato, lhy1024

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: returning gRPC Unavailable when PD is not serving.
Description check ✅ Passed The description covers the problem, change details, tests, release note, and commit message, and includes a linked issue reference.
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.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/grpc_service_test.go (1)

138-149: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a %w shutdown regression case.

This test only exercises errors.WithStack, so it will not catch a standard fmt.Errorf("...: %w", ...) wrapper being misclassified. Add an assertion for the wrapped error returning a nil response and codes.Unavailable. (pkg.go.dev)

Based on coding guidelines, %w wrapping is supported and sentinel checks should use errors.Is/As.

Proposed test addition
+	wrapped := fmt.Errorf("server is shutting down: %w", errs.ErrServerNotStarted.FastGenByArgs())
+	response, err = getMembersErrorResult(wrapped)
+	require.Nil(t, response)
+	require.Equal(t, codes.Unavailable, status.Code(err))
🤖 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 `@server/grpc_service_test.go` around lines 138 - 149, Add a standard
fmt.Errorf %w-wrapped ErrServerNotStarted case to TestGetMembersErrorResult,
asserting getMembersErrorResult returns a nil response and codes.Unavailable.
Ensure the implementation’s shutdown sentinel detection uses errors.Is/As so
both standard wrapping and errors.WithStack remain correctly classified.

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.

Inline comments:
In `@server/grpc_service.go`:
- Around line 468-476: The getMembersErrorResult helper currently misses
standard %w-wrapped ErrServerNotStarted values; update its classification check
to use stdlib errors.Is or equivalent traversal of both Cause and Unwrap while
preserving the existing direct and stack-wrapped behavior. In
server/grpc_service_test.go lines 138-149, add coverage for a %w-wrapped
ErrServerNotStarted case and verify it returns the unavailable error rather than
a response-level failure.

---

Nitpick comments:
In `@server/grpc_service_test.go`:
- Around line 138-149: Add a standard fmt.Errorf %w-wrapped ErrServerNotStarted
case to TestGetMembersErrorResult, asserting getMembersErrorResult returns a nil
response and codes.Unavailable. Ensure the implementation’s shutdown sentinel
detection uses errors.Is/As so both standard wrapping and errors.WithStack
remain correctly classified.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c371440-dab3-464d-b4e0-7c0364510224

📥 Commits

Reviewing files that changed from the base of the PR and between fad3ff9 and d4646b2.

📒 Files selected for processing (2)
  • server/grpc_service.go
  • server/grpc_service_test.go

Comment thread server/grpc_service.go
Comment on lines +468 to +476
func getMembersErrorResult(err error) (*pdpb.GetMembersResponse, error) {
if errors.ErrorEqual(err, errs.ErrServerNotStarted.FastGenByArgs()) {
return nil, errs.ErrNotStarted
}
// Keep other server-side failures in the response header so clients don't
// mistake them for service or transport availability failures.
return &pdpb.GetMembersResponse{
Header: grpcutil.WrapErrorToHeader(pdpb.ErrorType_UNKNOWN, err.Error()),
}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve ErrServerNotStarted through standard error wrapping.

The helper can misclassify %w-wrapped unavailable errors as response-level failures, while stack wrappers are correctly detected. Use stdlib errors.Is (or a check that follows both Cause() and Unwrap()), and add a %w-wrapped ErrServerNotStarted test case.

📍 Affects 2 files
  • server/grpc_service.go#L468-L476 (this comment)
  • server/grpc_service_test.go#L138-L149
🤖 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 `@server/grpc_service.go` around lines 468 - 476, The getMembersErrorResult
helper currently misses standard %w-wrapped ErrServerNotStarted values; update
its classification check to use stdlib errors.Is or equivalent traversal of both
Cause and Unwrap while preserving the existing direct and stack-wrapped
behavior. In server/grpc_service_test.go lines 138-149, add coverage for a
%w-wrapped ErrServerNotStarted case and verify it returns the unavailable error
rather than a response-level failure.

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.28%. Comparing base (070828c) to head (46eba19).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11066      +/-   ##
==========================================
+ Coverage   79.23%   79.28%   +0.04%     
==========================================
  Files         540      541       +1     
  Lines       76010    76356     +346     
==========================================
+ Hits        60226    60537     +311     
- Misses      11533    11562      +29     
- Partials     4251     4257       +6     
Flag Coverage Δ
unittests 79.28% <80.00%> (+0.04%) ⬆️

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rleungx
rleungx requested review from JmPotato and lhy1024 July 30, 2026 06:16
Signed-off-by: Ryan Leung <rleungx@gmail.com>
@ti-chi-bot ti-chi-bot Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jul 30, 2026
Signed-off-by: Ryan Leung <rleungx@gmail.com>
@rleungx

rleungx commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

/retest

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/grpc_service.go (1)

429-440: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check server state before rate limiting.

A closed server can currently return ErrGRPCRateLimitExceeded before reaching ErrNotStarted, violating the discovery RPC contract and masking the required Unavailable status. Move the IsClosed() check before rateLimitCheck().

Proposed fix
 func (s *GrpcServer) GetMembers(context.Context, *pdpb.GetMembersRequest) (*pdpb.GetMembersResponse, error) {
+	if s.IsClosed() {
+		return nil, errs.ErrNotStarted
+	}
 	done, err := s.rateLimitCheck()
 	if err != nil {
 		return nil, err
 	}
 	if done != nil {
 		defer done()
 	}
-	if s.IsClosed() {
-		return nil, errs.ErrNotStarted
-	}
🤖 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 `@server/grpc_service.go` around lines 429 - 440, Move the IsClosed() check in
the affected RPC handler before calling rateLimitCheck(), returning
errs.ErrNotStarted immediately for closed servers. Preserve the existing
rate-limit error handling and defer done() only after the server-state check
passes.
🤖 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.

Outside diff comments:
In `@server/grpc_service.go`:
- Around line 429-440: Move the IsClosed() check in the affected RPC handler
before calling rateLimitCheck(), returning errs.ErrNotStarted immediately for
closed servers. Preserve the existing rate-limit error handling and defer done()
only after the server-state check passes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 61e3b14e-215e-4886-95b9-47fc3a084464

📥 Commits

Reviewing files that changed from the base of the PR and between 2e5b0f7 and 46eba19.

📒 Files selected for processing (2)
  • server/grpc_service.go
  • server/grpc_service_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/grpc_service_test.go

@rleungx

rleungx commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

/retest

1 similar comment
@rleungx

rleungx commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

/retest

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. release-note Denotes a PR that will be considered when it comes time to generate release notes. 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.

1 participant