Skip to content

Commit 4a38849

Browse files
dangrasahidvelji
andauthored
feat(launchdarkly): Implement openfeature.StateHandler interface for clean shutdowns (#758)
Signed-off-by: Daniel Graña <[email protected]> Co-authored-by: Sahid Velji <[email protected]>
1 parent 5f29be0 commit 4a38849

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

providers/launchdarkly/pkg/provider.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ var errKeyMissing = errors.New("key and targetingKey attributes are missing, at
1919
// Scream at compile time if Provider does not implement FeatureProvider
2020
var _ openfeature.FeatureProvider = (*Provider)(nil)
2121

22+
// Scream at compile time if Provider does not implement StateHandler
23+
var _ openfeature.StateHandler = (*Provider)(nil)
24+
2225
// LDClient is the narrowed local interface for the parts of the
2326
// `*ld.LDClient` LaunchDarkly client used by the provider.
2427
type LDClient interface {
@@ -27,14 +30,16 @@ type LDClient interface {
2730
Float64VariationDetail(key string, context ldcontext.Context, defaultVal float64) (float64, ldreason.EvaluationDetail, error)
2831
StringVariationDetail(key string, context ldcontext.Context, defaultVal string) (string, ldreason.EvaluationDetail, error)
2932
JSONVariationDetail(key string, context ldcontext.Context, defaultVal ldvalue.Value) (ldvalue.Value, ldreason.EvaluationDetail, error)
33+
Close() error
3034
}
3135

3236
type Option func(*options)
3337

3438
// options contains all the optional arguments supported by Provider.
3539
type options struct {
36-
kindAttr string
37-
l Logger
40+
kindAttr string
41+
l Logger
42+
closeOnShutdown bool
3843
}
3944

4045
// WithLogger sets a logger implementation. By default a noop logger is used.
@@ -52,6 +57,14 @@ func WithKindAttr(name string) Option {
5257
}
5358
}
5459

60+
// WithCloseOnShutdown sets whether the LaunchDarkly client should be closed
61+
// when the provider is shut down. By default, this is false.
62+
func WithCloseOnShutdown(close bool) Option {
63+
return func(o *options) {
64+
o.closeOnShutdown = close
65+
}
66+
}
67+
5568
// Provider implements the FeatureProvider interface for LaunchDarkly.
5669
type Provider struct {
5770
options
@@ -372,3 +385,15 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flagKey string, default
372385
func (p *Provider) Hooks() []openfeature.Hook {
373386
return []openfeature.Hook{}
374387
}
388+
389+
func (p *Provider) Init(evaluationContext openfeature.EvaluationContext) error {
390+
return nil
391+
}
392+
393+
func (p *Provider) Shutdown() {
394+
if p.closeOnShutdown {
395+
if err := p.client.Close(); err != nil {
396+
p.l.Error("error during LaunchDarkly client shutdown: %s", err)
397+
}
398+
}
399+
}

providers/launchdarkly/pkg/provider_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,39 @@ func TestContextCancellation(t *testing.T) {
280280
_, err = client.ObjectValue(ctx, "rate_limit_config", nil, evalCtx)
281281
assert.Equals(t, errors.New("GENERAL: context canceled"), errors.Unwrap(err))
282282
}
283+
284+
// mockLDClient can be a struct that implements the LDClient interface for testing.
285+
type mockLDClient struct {
286+
ld.LDClient // Embedding the real client can be useful for mocking only specific methods
287+
closeCalled bool
288+
closeErr error
289+
}
290+
291+
func (c *mockLDClient) Close() error {
292+
c.closeCalled = true
293+
return c.closeErr
294+
}
295+
296+
func TestShutdown(t *testing.T) {
297+
t.Run("should not call client close on shutdown", func(t *testing.T) {
298+
mockClient := &mockLDClient{}
299+
provider := NewProvider(mockClient)
300+
301+
err := openfeature.SetProvider(provider)
302+
assert.Ok(t, err)
303+
304+
openfeature.Shutdown()
305+
assert.Cond(t, !mockClient.closeCalled, "expected client.Close() not to be called")
306+
})
307+
308+
t.Run("should call client close on shutdown", func(t *testing.T) {
309+
mockClient := &mockLDClient{}
310+
provider := NewProvider(mockClient, WithCloseOnShutdown(true))
311+
312+
err := openfeature.SetProvider(provider)
313+
assert.Ok(t, err)
314+
315+
openfeature.Shutdown()
316+
assert.Cond(t, mockClient.closeCalled, "expected client.Close() to be called")
317+
})
318+
}

0 commit comments

Comments
 (0)