@@ -10,13 +10,15 @@ import (
1010 "errors"
1111 "fmt"
1212 "sync"
13+ "time"
1314
1415 "github.com/DataDog/dd-trace-go/v2/internal"
1516 "github.com/DataDog/dd-trace-go/v2/internal/log"
1617 "github.com/open-feature/go-sdk/openfeature"
1718)
1819
1920var _ openfeature.FeatureProvider = (* DatadogProvider )(nil )
21+ var _ openfeature.ContextAwareStateHandler = (* DatadogProvider )(nil )
2022
2123// Sentinel errors for error classification
2224var (
@@ -35,7 +37,8 @@ type DatadogProvider struct {
3537 configuration * universalFlagsConfiguration
3638 metadata openfeature.Metadata
3739
38- configChange sync.Cond
40+ configReady chan struct {} // Closed when first config is loaded
41+ configOnce sync.Once // Ensure channel is closed only once
3942}
4043
4144type ProviderConfig struct {
@@ -61,22 +64,29 @@ func NewDatadogProvider(ProviderConfig) (openfeature.FeatureProvider, error) {
6164}
6265
6366func newDatadogProvider () * DatadogProvider {
64- p := & DatadogProvider {
67+ return & DatadogProvider {
6568 metadata : openfeature.Metadata {
6669 Name : "Datadog Remote Config Provider" ,
6770 },
71+ configReady : make (chan struct {}),
6872 }
69- p .configChange .L = & p .mu
70- return p
7173}
7274
7375// updateConfiguration updates the provider's flag configuration.
7476// This is called by the Remote Config callback when new configuration is received.
7577func (p * DatadogProvider ) updateConfiguration (config * universalFlagsConfiguration ) {
7678 p .mu .Lock ()
7779 defer p .mu .Unlock ()
80+
81+ isFirstConfig := p .configuration == nil
7882 p .configuration = config
79- p .configChange .Broadcast ()
83+
84+ // Close the channel on first configuration to signal readiness
85+ if isFirstConfig {
86+ p .configOnce .Do (func () {
87+ close (p .configReady )
88+ })
89+ }
8090}
8191
8292// getConfiguration returns the current configuration (for testing purposes).
@@ -93,30 +103,73 @@ func (p *DatadogProvider) Metadata() openfeature.Metadata {
93103
94104// Init initializes the provider. For the Datadog provider,
95105// this is waiting for the first configuration to be loaded.
96- func (p * DatadogProvider ) Init (openfeature.EvaluationContext ) error {
97- p . mu . Lock ()
98- defer p . mu . Unlock ( )
99- for p . configuration == nil {
100- p . configChange . Wait ( )
101- }
106+ func (p * DatadogProvider ) Init (evaluationContext openfeature.EvaluationContext ) error {
107+ // Use a background context with a reasonable timeout for backward compatibility
108+ ctx , cancel := context . WithTimeout ( context . Background (), 30 * time . Second )
109+ defer cancel ()
110+ return p . InitWithContext ( ctx , evaluationContext )
111+ }
102112
103- return nil
113+ // InitWithContext initializes the provider with context support.
114+ // This method respects context cancellation and timeouts, allowing users
115+ // to cancel the initialization process if needed.
116+ func (p * DatadogProvider ) InitWithContext (ctx context.Context , _ openfeature.EvaluationContext ) error {
117+ // Quick check if already configured
118+ p .mu .RLock ()
119+ if p .configuration != nil {
120+ p .mu .RUnlock ()
121+ return nil
122+ }
123+ readyChan := p .configReady
124+ p .mu .RUnlock ()
125+
126+ // Wait for either context cancellation or configuration
127+ select {
128+ case <- ctx .Done ():
129+ return ctx .Err ()
130+ case <- readyChan :
131+ return nil
132+ }
104133}
105134
106135// Shutdown shuts down the provider and stops Remote Config updates.
107136func (p * DatadogProvider ) Shutdown () {
108- // Best effort to stop Remote Config - ignore error as we're shutting down anyway
109- _ = stopRemoteConfig ()
137+ // Use a background context with a reasonable timeout for backward compatibility
138+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
139+ defer cancel ()
140+ _ = p .ShutdownWithContext (ctx )
141+ }
142+
143+ // ShutdownWithContext shuts down the provider with context support.
144+ // This method respects context cancellation and timeouts, allowing users
145+ // to control how long the shutdown process should take.
146+ func (p * DatadogProvider ) ShutdownWithContext (ctx context.Context ) error {
147+ // Create a channel to signal completion
148+ done := make (chan error , 1 )
149+
150+ go func () {
151+ // Perform the shutdown operations
152+ err := stopRemoteConfig ()
153+ done <- err
154+ }()
155+
156+ // Wait for completion or context cancellation
157+ select {
158+ case <- ctx .Done ():
159+ return ctx .Err ()
160+ case err := <- done :
161+ return err
162+ }
110163}
111164
112165// BooleanEvaluation evaluates a boolean feature flag.
113166func (p * DatadogProvider ) BooleanEvaluation (
114- _ context.Context ,
167+ ctx context.Context ,
115168 flagKey string ,
116169 defaultValue bool ,
117170 flatCtx openfeature.FlattenedContext ,
118171) openfeature.BoolResolutionDetail {
119- result := p .evaluate (flagKey , defaultValue , flatCtx )
172+ result := p .evaluate (ctx , flagKey , defaultValue , flatCtx )
120173
121174 // Convert result to boolean
122175 boolValue , ok := result .Value .(bool )
@@ -138,12 +191,12 @@ func (p *DatadogProvider) BooleanEvaluation(
138191
139192// StringEvaluation evaluates a string feature flag.
140193func (p * DatadogProvider ) StringEvaluation (
141- _ context.Context ,
194+ ctx context.Context ,
142195 flagKey string ,
143196 defaultValue string ,
144197 flatCtx openfeature.FlattenedContext ,
145198) openfeature.StringResolutionDetail {
146- result := p .evaluate (flagKey , defaultValue , flatCtx )
199+ result := p .evaluate (ctx , flagKey , defaultValue , flatCtx )
147200
148201 // Convert result to string
149202 strValue , ok := result .Value .(string )
@@ -165,12 +218,12 @@ func (p *DatadogProvider) StringEvaluation(
165218
166219// FloatEvaluation evaluates a numeric (float) feature flag.
167220func (p * DatadogProvider ) FloatEvaluation (
168- _ context.Context ,
221+ ctx context.Context ,
169222 flagKey string ,
170223 defaultValue float64 ,
171224 flatCtx openfeature.FlattenedContext ,
172225) openfeature.FloatResolutionDetail {
173- result := p .evaluate (flagKey , defaultValue , flatCtx )
226+ result := p .evaluate (ctx , flagKey , defaultValue , flatCtx )
174227
175228 // Convert result to float64
176229 var floatValue float64
@@ -211,12 +264,12 @@ func (p *DatadogProvider) FloatEvaluation(
211264
212265// IntEvaluation evaluates an integer feature flag.
213266func (p * DatadogProvider ) IntEvaluation (
214- _ context.Context ,
267+ ctx context.Context ,
215268 flagKey string ,
216269 defaultValue int64 ,
217270 flatCtx openfeature.FlattenedContext ,
218271) openfeature.IntResolutionDetail {
219- result := p .evaluate (flagKey , defaultValue , flatCtx )
272+ result := p .evaluate (ctx , flagKey , defaultValue , flatCtx )
220273
221274 // Convert result to int64
222275 var intValue int64
@@ -264,12 +317,12 @@ func (p *DatadogProvider) IntEvaluation(
264317
265318// ObjectEvaluation evaluates a structured (JSON) feature flag.
266319func (p * DatadogProvider ) ObjectEvaluation (
267- _ context.Context ,
320+ ctx context.Context ,
268321 flagKey string ,
269322 defaultValue any ,
270323 flatCtx openfeature.FlattenedContext ,
271324) openfeature.InterfaceResolutionDetail {
272- result := p .evaluate (flagKey , defaultValue , flatCtx )
325+ result := p .evaluate (ctx , flagKey , defaultValue , flatCtx )
273326
274327 return openfeature.InterfaceResolutionDetail {
275328 Value : result .Value ,
@@ -289,6 +342,7 @@ func (p *DatadogProvider) Hooks() []openfeature.Hook {
289342
290343// evaluate is the core evaluation method that all type-specific methods use.
291344func (p * DatadogProvider ) evaluate (
345+ ctx context.Context ,
292346 flagKey string ,
293347 defaultValue any ,
294348 flatCtx openfeature.FlattenedContext ,
@@ -297,6 +351,18 @@ func (p *DatadogProvider) evaluate(
297351 defer func () {
298352 log .Debug ("openfeature: evaluated flag %q: value=%v, reason=%s, error=%v" , flagKey , res .Value , res .Reason , res .Error )
299353 }()
354+
355+ // Check if context was cancelled before starting evaluation
356+ select {
357+ case <- ctx .Done ():
358+ return evaluationResult {
359+ Value : defaultValue ,
360+ Reason : openfeature .ErrorReason ,
361+ Error : ctx .Err (),
362+ }
363+ default :
364+ }
365+
300366 config := p .getConfiguration ()
301367
302368 // Check if configuration is loaded
@@ -318,7 +384,7 @@ func (p *DatadogProvider) evaluate(
318384 }
319385 }
320386
321- // Evaluate the flag
387+ // Evaluate the flag (pass context for potential future use in evaluateFlag)
322388 return evaluateFlag (flag , defaultValue , flatCtx )
323389}
324390
0 commit comments