Skip to content

Commit ca79823

Browse files
chore: make backoff configurable in grpc retry policy, have consistent behaviour to Java flagd provider
Signed-off-by: Alexandra Oberaigner <[email protected]>
1 parent 979b398 commit ca79823

File tree

8 files changed

+95
-14
lines changed

8 files changed

+95
-14
lines changed

providers/flagd/e2e/inprocess_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package e2e
44

55
import (
6+
flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg"
67
"testing"
78

89
"github.com/open-feature/go-sdk-contrib/tests/flagd/testframework"
@@ -17,6 +18,9 @@ func TestInProcessProviderE2E(t *testing.T) {
1718
runner := testframework.NewTestbedRunner(testframework.TestbedConfig{
1819
ResolverType: testframework.InProcess,
1920
TestbedConfig: "default",
21+
ExtraOptions: []flagd.ProviderOption{
22+
flagd.WithRetryBackoffMaxMs(5000),
23+
},
2024
})
2125
defer runner.Cleanup()
2226

providers/flagd/pkg/configuration.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const (
4949
flagdOfflinePathEnvironmentVariableName = "FLAGD_OFFLINE_FLAG_SOURCE_PATH"
5050
flagdTargetUriEnvironmentVariableName = "FLAGD_TARGET_URI"
5151
flagdGracePeriodVariableName = "FLAGD_RETRY_GRACE_PERIOD"
52+
flagdRetryBackoffMsVariableName = "FLAGD_RETRY_BACKOFF_MS"
53+
flagdRetryBackoffMaxMsVariableName = "FLAGD_RETRY_BACKOFF_MAX_MS"
5254
flagdFatalStatusCodesVariableName = "FLAGD_FATAL_STATUS_CODES"
5355
)
5456

@@ -71,8 +73,8 @@ type ProviderConfiguration struct {
7173
CustomSyncProviderUri string
7274
GrpcDialOptionsOverride []grpc.DialOption
7375
RetryGracePeriod int
74-
RetryBackoffMs int
75-
RetryBackoffMaxMs int
76+
RetryBackoffMs int
77+
RetryBackoffMaxMs int
7678
FatalStatusCodes []string
7779

7880
log logr.Logger
@@ -88,6 +90,8 @@ func newDefaultConfiguration(log logr.Logger) *ProviderConfiguration {
8890
Resolver: defaultResolver,
8991
Tls: defaultTLS,
9092
RetryGracePeriod: defaultGracePeriod,
93+
RetryBackoffMs: defaultRetryBackoffMs,
94+
RetryBackoffMaxMs: defaultRetryBackoffMaxMs,
9195
}
9296

9397
p.updateFromEnvVar()
@@ -218,6 +222,8 @@ func (cfg *ProviderConfiguration) updateFromEnvVar() {
218222
}
219223

220224
cfg.RetryGracePeriod = getIntFromEnvVarOrDefault(flagdGracePeriodVariableName, defaultGracePeriod, cfg.log)
225+
cfg.RetryBackoffMs = getIntFromEnvVarOrDefault(flagdRetryBackoffMsVariableName, defaultRetryBackoffMs, cfg.log)
226+
cfg.RetryBackoffMaxMs = getIntFromEnvVarOrDefault(flagdRetryBackoffMaxMsVariableName, defaultRetryBackoffMaxMs, cfg.log)
221227

222228
var fatalStatusCodes string
223229
if envVal := os.Getenv(flagdFatalStatusCodesVariableName); envVal != "" {

providers/flagd/pkg/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ func NewProvider(opts ...ProviderOption) (*Provider, error) {
7474
CustomSyncProviderUri: provider.providerConfiguration.CustomSyncProviderUri,
7575
GrpcDialOptionsOverride: provider.providerConfiguration.GrpcDialOptionsOverride,
7676
RetryGracePeriod: provider.providerConfiguration.RetryGracePeriod,
77+
RetryBackOffMs: provider.providerConfiguration.RetryBackoffMs,
78+
RetryBackOffMaxMs: provider.providerConfiguration.RetryBackoffMaxMs,
7779
FatalStatusCodes: provider.providerConfiguration.FatalStatusCodes,
7880
})
7981
default:

providers/flagd/pkg/service/in_process/grpc_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ func (g *Sync) buildRetryPolicy() string {
3030
},
3131
"retryPolicy": RetryPolicy{
3232
MaxAttempts: 3,
33-
InitialBackoff: "1s",
34-
MaxBackoff: "5s",
33+
InitialBackoff: (time.Duration(g.RetryBackOffMs) * time.Millisecond).String(),
34+
MaxBackoff: (time.Duration(g.RetryBackOffMaxMs) * time.Millisecond).String(),
3535
BackoffMultiplier: 2.0,
3636
RetryableStatusCodes: []string{"UNKNOWN", "UNAVAILABLE"},
3737
},

providers/flagd/pkg/service/in_process/grpc_config_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,74 @@
11
package process
22

33
import (
4+
"encoding/json"
45
"github.com/open-feature/flagd/core/pkg/logger"
56
"go.uber.org/zap"
67
"google.golang.org/grpc/codes"
8+
"strings"
79
"testing"
810
)
911

12+
func TestBuildRetryPolicy(t *testing.T) {
13+
g := &Sync{
14+
RetryBackOffMs: 100,
15+
RetryBackOffMaxMs: 500,
16+
}
17+
18+
result := g.buildRetryPolicy()
19+
20+
// Unmarshal to check structure
21+
var policy map[string]interface{}
22+
if err := json.Unmarshal([]byte(result), &policy); err != nil {
23+
t.Fatalf("Failed to unmarshal result: %v", err)
24+
}
25+
26+
methodConfig, ok := policy["methodConfig"].([]interface{})
27+
if !ok || len(methodConfig) == 0 {
28+
t.Fatalf("methodConfig missing or empty")
29+
}
30+
31+
config := methodConfig[0].(map[string]interface{})
32+
retryPolicy, ok := config["retryPolicy"].(map[string]interface{})
33+
if !ok {
34+
t.Fatalf("retryPolicy missing")
35+
}
36+
37+
if retryPolicy["MaxAttempts"].(float64) != 3 {
38+
t.Errorf("MaxAttempts = %v; want 3", retryPolicy["MaxAttempts"])
39+
}
40+
if retryPolicy["InitialBackoff"].(string) != "100ms" {
41+
t.Errorf("InitialBackoff = %v; want 100ms", retryPolicy["InitialBackoff"])
42+
}
43+
if retryPolicy["MaxBackoff"].(string) != "500ms" {
44+
t.Errorf("MaxBackoff = %v; want 500ms", retryPolicy["MaxBackoff"])
45+
}
46+
if retryPolicy["BackoffMultiplier"].(float64) != 2.0 {
47+
t.Errorf("BackoffMultiplier = %v; want 2.0", retryPolicy["BackoffMultiplier"])
48+
}
49+
codes := retryPolicy["RetryableStatusCodes"].([]interface{})
50+
expectedCodes := []string{"UNKNOWN", "UNAVAILABLE"}
51+
for i, code := range expectedCodes {
52+
if codes[i].(string) != code {
53+
t.Errorf("RetryableStatusCodes[%d] = %v; want %v", i, codes[i], code)
54+
}
55+
}
56+
57+
// Also check that the result is valid JSON and contains expected substrings
58+
if !strings.Contains(result, `"MaxAttempts":3`) {
59+
t.Error("Result does not contain MaxAttempts")
60+
}
61+
if !strings.Contains(result, `"InitialBackoff":"100ms"`) {
62+
t.Error("Result does not contain InitialBackoff")
63+
}
64+
if !strings.Contains(result, `"MaxBackoff":"500ms"`) {
65+
t.Error("Result does not contain MaxBackoff")
66+
}
67+
if !strings.Contains(result, `"RetryableStatusCodes":["UNKNOWN","UNAVAILABLE"]`) {
68+
t.Error("Result does not contain RetryableStatusCodes")
69+
}
70+
}
71+
1072
func TestSync_initNonRetryableStatusCodesSet(t *testing.T) {
1173
tests := []struct {
1274
name string

providers/flagd/pkg/service/in_process/grpc_sync.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type Sync struct {
3838
Selector string
3939
URI string
4040
MaxMsgSize int
41+
RetryGracePeriod int
42+
RetryBackOffMs int
43+
RetryBackOffMaxMs int
4144
FatalStatusCodes []string
4245

4346
// Runtime state
@@ -191,6 +194,9 @@ func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
191194
}
192195
}
193196

197+
// Backoff before retrying
198+
time.Sleep(time.Duration(g.RetryBackOffMaxMs) * time.Millisecond)
199+
194200
g.Logger.Warn(fmt.Sprintf("sync cycle failed: %v, retrying...", err))
195201
g.sendEvent(ctx, SyncEvent{event: of.ProviderError})
196202

providers/flagd/pkg/service/in_process/service_grpc_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ func TestInProcessProviderEvaluation(t *testing.T) {
5050
}
5151

5252
inProcessService := NewInProcessService(Configuration{
53-
Host: host,
54-
Port: port,
55-
Selector: scope,
56-
TLSEnabled: false,
53+
Host: host,
54+
Port: port,
55+
Selector: scope,
56+
TLSEnabled: false,
57+
RetryBackOffMaxMs: 5000,
58+
RetryBackOffMs: 1000,
5759
})
5860

5961
// when
@@ -138,9 +140,11 @@ func TestInProcessProviderEvaluationEnvoy(t *testing.T) {
138140
}
139141

140142
inProcessService := NewInProcessService(Configuration{
141-
TargetUri: "envoy://localhost:9211/foo.service",
142-
Selector: scope,
143-
TLSEnabled: false,
143+
TargetUri: "envoy://localhost:9211/foo.service",
144+
Selector: scope,
145+
TLSEnabled: false,
146+
RetryBackOffMaxMs: 5000,
147+
RetryBackOffMs: 1000,
144148
})
145149

146150
// when
@@ -201,7 +205,6 @@ func TestInProcessProviderEvaluationEnvoy(t *testing.T) {
201205
}
202206
}
203207

204-
205208
// bufferedServer - a mock grpc service backed by buffered connection
206209
type bufferedServer struct {
207210
listener net.Listener

tests/flagd/testframework/config_steps.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ var ignoredOptions = []string{
1717
"deadlineMs",
1818
"streamDeadlineMs",
1919
"keepAliveTime",
20-
"retryBackoffMs",
21-
"retryBackoffMaxMs",
2220
"offlinePollIntervalMs",
2321
}
2422

0 commit comments

Comments
 (0)