Skip to content

Commit ea7096d

Browse files
authored
feat(v2): update Invoke to add retry count to context (#462)
1 parent 2862fc8 commit ea7096d

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

v2/invoke.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,26 @@ package gax
3131

3232
import (
3333
"context"
34+
"strconv"
3435
"strings"
3536
"time"
3637

3738
"github.com/googleapis/gax-go/v2/apierror"
39+
"google.golang.org/grpc/metadata"
3840
)
3941

4042
// APICall is a user defined call stub.
4143
type APICall func(context.Context, CallSettings) error
4244

45+
// withRetryCount returns a new context with the retry count appended to
46+
// gRPC metadata. The retry count is the number of retries that have been
47+
// attempted. On the initial request, retry count is 0.
48+
// On a second request (the first retry), retry count is 1.
49+
func withRetryCount(ctx context.Context, retryCount int) context.Context {
50+
// Add to gRPC metadata so it's visible to StatsHandlers
51+
return metadata.AppendToOutgoingContext(ctx, "gcp.grpc.resend_count", strconv.Itoa(retryCount))
52+
}
53+
4354
// Invoke calls the given APICall, performing retries as specified by opts, if
4455
// any.
4556
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
@@ -78,8 +89,15 @@ func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper
7889
ctx = c
7990
}
8091

92+
retryCount := 0
93+
// Feature gate: GOOGLE_SDK_GO_EXPERIMENTAL_TRACING=true
94+
tracingEnabled := IsFeatureEnabled("TRACING")
8195
for {
82-
err := call(ctx, settings)
96+
ctxToUse := ctx
97+
if tracingEnabled {
98+
ctxToUse = withRetryCount(ctx, retryCount)
99+
}
100+
err := call(ctxToUse, settings)
83101
if err == nil {
84102
return nil
85103
}
@@ -110,5 +128,6 @@ func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper
110128
} else if err = sp(ctx, d); err != nil {
111129
return err
112130
}
131+
retryCount++
113132
}
114133
}

v2/invoke_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ package gax
3232
import (
3333
"context"
3434
"errors"
35+
"fmt"
36+
"strconv"
3537
"testing"
3638
"time"
3739

@@ -40,6 +42,7 @@ import (
4042
"github.com/googleapis/gax-go/v2/apierror"
4143
"google.golang.org/genproto/googleapis/rpc/errdetails"
4244
"google.golang.org/grpc/codes"
45+
"google.golang.org/grpc/metadata"
4346
"google.golang.org/grpc/status"
4447
)
4548

@@ -264,3 +267,47 @@ func TestInvokeWithTimeout(t *testing.T) {
264267
})
265268
}
266269
}
270+
271+
func TestInvokeRetryCount(t *testing.T) {
272+
for _, tracingEnabled := range []bool{true, false} {
273+
t.Run(fmt.Sprintf("tracingEnabled=%v", tracingEnabled), func(t *testing.T) {
274+
TestOnlyResetIsFeatureEnabled()
275+
defer TestOnlyResetIsFeatureEnabled()
276+
277+
if tracingEnabled {
278+
t.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_TRACING", "true")
279+
} else {
280+
t.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_TRACING", "false")
281+
}
282+
283+
const target = 3
284+
var retryCounts []int
285+
calls := 0
286+
apiCall := func(ctx context.Context, _ CallSettings) error {
287+
calls++
288+
md, _ := metadata.FromOutgoingContext(ctx)
289+
if vals := md["gcp.grpc.resend_count"]; len(vals) > 0 {
290+
if count, err := strconv.Atoi(vals[0]); err == nil {
291+
retryCounts = append(retryCounts, count)
292+
}
293+
}
294+
if calls < target {
295+
return errors.New("retry")
296+
}
297+
return nil
298+
}
299+
var settings CallSettings
300+
WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings)
301+
var sp recordSleeper
302+
invoke(context.Background(), apiCall, settings, sp.sleep)
303+
304+
var want []int
305+
if tracingEnabled {
306+
want = []int{0, 1, 2}
307+
}
308+
if diff := cmp.Diff(want, retryCounts); diff != "" {
309+
t.Errorf("retry count mismatch (-want +got):\n%s", diff)
310+
}
311+
})
312+
}
313+
}

0 commit comments

Comments
 (0)