Repository: tikv/client-go (go)

Issue:
Don't invalid region in tryFollowers when set user-defined req timeout and meet DeadLineExceededErr
# Issue

### 1. Minimal reproduce step

In the following case, suppose the region leader is in `store1`.

```go
	ca = replicaSelectorAccessPathCase{
		reqType:   tikvrpc.CmdGet,
		readType:  kv.ReplicaReadMixed,
		staleRead: true,
		timeout:   time.Second,
		label:     nil,
		accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr, ServerIsBusyErr},
```

### 2. What did you expect to see?

No backoff.

```go
		expect: &accessPathResult{
			accessPath: []string{
				"{addr: store1, replica-read: false, stale-read: true}",
				"{addr: store2, replica-read: true, stale-read: false}",
				"{addr: store3, replica-read: true, stale-read: false}"},
			regionIsValid:   true,
		},
```


### 3. What did you see instead?

```go
		expect: &accessPathResult{
			...
			regionIsValid:   false,
		},
```

The request has user-defined timeout 1s, the replica selector logic is:

1. try store1 by stale-read, but got DeadLineExceededErr.
2. retry store2, got `ServerIsBusyErr`.
3. retry store3, got `ServerIsBusyErr` too, and then invalidate the region in cache.

The expected result is doesn't invalidate region in cache, and the do fast retry without user-defined timeout.


Pull request:
Don't invalid region in tryFollowers when set user-defined req timeout and meet DeadLineExceededErr
close #1170

Patch:
diff --git a/internal/locate/region_request.go b/internal/locate/region_request.go
index 277899d..5d7ad89 100644
--- a/internal/locate/region_request.go
+++ b/internal/locate/region_request.go
@@ -481,7 +481,6 @@ type tryFollower struct {
 }
 
 func (state *tryFollower) next(bo *retry.Backoffer, selector *replicaSelector) (*RPCContext, error) {
-	hasDeadlineExceededErr := false
 	filterReplicas := func(fn func(*replica) bool) (AccessIndex, *replica) {
 		for i := 0; i < len(selector.replicas); i++ {
 			idx := AccessIndex((int(state.lastIdx) + i) % len(selector.replicas))
@@ -489,7 +488,6 @@ func (state *tryFollower) next(bo *retry.Backoffer, selector *replicaSelector) (
 				continue
 			}
 			selectReplica := selector.replicas[idx]
-			hasDeadlineExceededErr = hasDeadlineExceededErr || selectReplica.deadlineErrUsingConfTimeout
 			if selectReplica.store.getLivenessState() != unreachable && !selectReplica.deadlineErrUsingConfTimeout &&
 				fn(selectReplica) {
 				return idx, selectReplica
@@ -523,9 +521,11 @@ func (state *tryFollower) next(bo *retry.Backoffer, selector *replicaSelector) (
 
 	// If all followers are tried and fail, backoff and retry.
 	if selector.targetIdx < 0 {
-		if hasDeadlineExceededErr {
-			// when meet deadline exceeded error, do fast retry without invalidate region cache.
-			return nil, nil
+		for _, replica := range selector.replicas {
+			if replica.deadlineErrUsingConfTimeout {
+				// when meet deadline exceeded error, do fast retry without invalidate region cache.
+				return nil, nil
+			}
 		}
 		metrics.TiKVReplicaSelectorFailureCounter.WithLabelValues("exhausted").Inc()
 		selector.invalidateRegion()


Test patch:
diff --git a/internal/locate/replica_selector_test.go b/internal/locate/replica_selector_test.go
index 0df8a09..6b9d15f 100644
--- a/internal/locate/replica_selector_test.go
+++ b/internal/locate/replica_selector_test.go
@@ -278,7 +278,29 @@ func TestReplicaReadAccessPathByCase(t *testing.T) {
 			respRegionError: fakeEpochNotMatch,
 			backoffCnt:      1,
 			backoffDetail:   []string{"regionScheduling+1"},
-			regionIsValid:   false,
+			regionIsValid:   true,
+		},
+	}
+	s.True(s.runCaseAndCompare(ca))
+
+	// Don't invalid region in tryFollowers, since leader meets deadlineExceededErr.
+	ca = replicaSelectorAccessPathCase{
+		reqType:   tikvrpc.CmdGet,
+		readType:  kv.ReplicaReadMixed,
+		staleRead: true,
+		timeout:   time.Second,
+		label:     nil,
+		accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr, ServerIsBusyErr},
+		expect: &accessPathResult{
+			accessPath: []string{
+				"{addr: store1, replica-read: false, stale-read: true}",
+				"{addr: store2, replica-read: true, stale-read: false}",
+				"{addr: store3, replica-read: true, stale-read: false}"},
+			respErr:         "",
+			respRegionError: fakeEpochNotMatch,
+			backoffCnt:      2,
+			backoffDetail:   []string{"tikvServerBusy+2"},
+			regionIsValid:   true,
 		},
 	}
 	s.True(s.runCaseAndCompare(ca))