Skip to content

Commit 6f71fe4

Browse files
feat: blocking provider mutator (#251)
* introduce wait apis and improve existing tests Signed-off-by: Kavindu Dodanduwa <[email protected]> * tests for new apis Signed-off-by: Kavindu Dodanduwa <[email protected]> * improve race conditions Signed-off-by: Kavindu Dodanduwa <[email protected]> * return error for synchronous provider registrations Signed-off-by: Kavindu Dodanduwa <[email protected]> --------- Signed-off-by: Kavindu Dodanduwa <[email protected]>
1 parent eaefcc8 commit 6f71fe4

File tree

5 files changed

+240
-60
lines changed

5 files changed

+240
-60
lines changed

openfeature/api.go

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package openfeature
22

33
import (
44
"errors"
5+
"fmt"
56
"sync"
67

78
"github.com/go-logr/logr"
@@ -14,7 +15,7 @@ type evaluationAPI struct {
1415
defaultProvider FeatureProvider
1516
namedProviders map[string]FeatureProvider
1617
hks []Hook
17-
evalCtx EvaluationContext
18+
apiCtx EvaluationContext
1819
logger logr.Logger
1920
mu sync.RWMutex
2021
eventExecutor *eventExecutor
@@ -28,29 +29,32 @@ func newEvaluationAPI() evaluationAPI {
2829
defaultProvider: NoopProvider{},
2930
namedProviders: map[string]FeatureProvider{},
3031
hks: []Hook{},
31-
evalCtx: EvaluationContext{},
32+
apiCtx: EvaluationContext{},
3233
logger: logger,
3334
mu: sync.RWMutex{},
3435
eventExecutor: newEventExecutor(logger),
3536
}
3637
}
3738

38-
// setProvider sets the default FeatureProvider of the evaluationAPI. Returns an error if FeatureProvider is nil
39-
func (api *evaluationAPI) setProvider(provider FeatureProvider) error {
39+
// setProvider sets the default FeatureProvider of the evaluationAPI.
40+
// Returns an error if provider registration cause an error
41+
func (api *evaluationAPI) setProvider(provider FeatureProvider, async bool) error {
4042
api.mu.Lock()
4143
defer api.mu.Unlock()
4244

4345
if provider == nil {
4446
return errors.New("default provider cannot be set to nil")
4547
}
4648

47-
// Initialize new default provider and shutdown the old one
48-
// Provider update must be non-blocking, hence initialization & shutdown happens concurrently
4949
oldProvider := api.defaultProvider
5050
api.defaultProvider = provider
5151

52-
api.initNewAndShutdownOld(provider, oldProvider)
53-
err := api.eventExecutor.registerDefaultProvider(provider)
52+
err := api.initNewAndShutdownOld(provider, oldProvider, async)
53+
if err != nil {
54+
return err
55+
}
56+
57+
err = api.eventExecutor.registerDefaultProvider(provider)
5458
if err != nil {
5559
return err
5660
}
@@ -67,7 +71,7 @@ func (api *evaluationAPI) getProvider() FeatureProvider {
6771
}
6872

6973
// setProvider sets a provider with client name. Returns an error if FeatureProvider is nil
70-
func (api *evaluationAPI) setNamedProvider(clientName string, provider FeatureProvider) error {
74+
func (api *evaluationAPI) setNamedProvider(clientName string, provider FeatureProvider, async bool) error {
7175
api.mu.Lock()
7276
defer api.mu.Unlock()
7377

@@ -80,8 +84,12 @@ func (api *evaluationAPI) setNamedProvider(clientName string, provider FeaturePr
8084
oldProvider := api.namedProviders[clientName]
8185
api.namedProviders[clientName] = provider
8286

83-
api.initNewAndShutdownOld(provider, oldProvider)
84-
err := api.eventExecutor.registerNamedEventingProvider(clientName, provider)
87+
err := api.initNewAndShutdownOld(provider, oldProvider, async)
88+
if err != nil {
89+
return err
90+
}
91+
92+
err = api.eventExecutor.registerNamedEventingProvider(clientName, provider)
8593
if err != nil {
8694
return err
8795
}
@@ -97,11 +105,11 @@ func (api *evaluationAPI) getNamedProviders() map[string]FeatureProvider {
97105
return api.namedProviders
98106
}
99107

100-
func (api *evaluationAPI) setEvaluationContext(evalCtx EvaluationContext) {
108+
func (api *evaluationAPI) setEvaluationContext(apiCtx EvaluationContext) {
101109
api.mu.Lock()
102110
defer api.mu.Unlock()
103111

104-
api.evalCtx = evalCtx
112+
api.apiCtx = apiCtx
105113
}
106114

107115
func (api *evaluationAPI) setLogger(l logr.Logger) {
@@ -163,52 +171,68 @@ func (api *evaluationAPI) forTransaction(clientName string) (FeatureProvider, []
163171
provider = api.defaultProvider
164172
}
165173

166-
return provider, api.hks, api.evalCtx
174+
return provider, api.hks, api.apiCtx
167175
}
168176

169177
// initNewAndShutdownOld is a helper to initialise new FeatureProvider and shutdown the old FeatureProvider.
170-
// Operations happen concurrently.
171-
func (api *evaluationAPI) initNewAndShutdownOld(newProvider FeatureProvider, oldProvider FeatureProvider) {
172-
v, ok := newProvider.(StateHandler)
173-
if ok && v.Status() == NotReadyState {
174-
go func(provider FeatureProvider, stateHandler StateHandler, evalCtx EvaluationContext, eventChan chan eventPayload) {
175-
err := stateHandler.Init(evalCtx)
176-
// emit ready/error event once initialization is complete
177-
if err != nil {
178-
eventChan <- eventPayload{
179-
Event{
180-
ProviderName: provider.Metadata().Name,
181-
EventType: ProviderError,
182-
ProviderEventDetails: ProviderEventDetails{},
183-
}, provider,
184-
}
185-
} else {
186-
eventChan <- eventPayload{
187-
Event{
188-
ProviderName: provider.Metadata().Name,
189-
EventType: ProviderReady,
190-
ProviderEventDetails: ProviderEventDetails{},
191-
}, provider,
192-
}
193-
}
194-
}(newProvider, v, api.evalCtx, api.eventExecutor.eventChan)
195-
}
196-
197-
v, ok = oldProvider.(StateHandler)
178+
func (api *evaluationAPI) initNewAndShutdownOld(newProvider FeatureProvider, oldProvider FeatureProvider, async bool) error {
179+
if async {
180+
go func(executor *eventExecutor, ctx EvaluationContext) {
181+
// for async initialization, error is conveyed as an event
182+
event, _ := initializer(newProvider, ctx)
183+
executor.triggerEvent(event, newProvider)
184+
}(api.eventExecutor, api.apiCtx)
185+
} else {
186+
event, err := initializer(newProvider, api.apiCtx)
187+
api.eventExecutor.triggerEvent(event, newProvider)
188+
if err != nil {
189+
return err
190+
}
191+
}
192+
193+
v, ok := oldProvider.(StateHandler)
198194

199195
// oldProvider can be nil or without state handling capability
200196
if oldProvider == nil || !ok {
201-
return
197+
return nil
202198
}
203199

204200
// check for multiple bindings
205201
if oldProvider == api.defaultProvider || contains(oldProvider, maps.Values(api.namedProviders)) {
206-
return
202+
return nil
207203
}
208204

209205
go func(forShutdown StateHandler) {
210206
forShutdown.Shutdown()
211207
}(v)
208+
209+
return nil
210+
}
211+
212+
// initializer is a helper to execute provider initialization and generate appropriate event for the initialization
213+
// It also returns an error if the initialization resulted in an error
214+
func initializer(provider FeatureProvider, apiCtx EvaluationContext) (Event, error) {
215+
var event = Event{
216+
ProviderName: provider.Metadata().Name,
217+
EventType: ProviderReady,
218+
ProviderEventDetails: ProviderEventDetails{
219+
Message: "Provider initialization successful",
220+
},
221+
}
222+
223+
handler, ok := provider.(StateHandler)
224+
if !ok {
225+
// Note - a provider without state handling capability can be assumed to be ready immediately.
226+
return event, nil
227+
}
228+
229+
err := handler.Init(apiCtx)
230+
if err != nil {
231+
event.EventType = ProviderError
232+
event.Message = fmt.Sprintf("Provider initialization error, %v", err)
233+
}
234+
235+
return event, err
212236
}
213237

214238
func contains(provider FeatureProvider, in []FeatureProvider) bool {

openfeature/event_executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func (e *eventExecutor) triggerEvent(event Event, handler FeatureProvider) {
327327
}
328328
}
329329

330-
if e.defaultProviderReference.featureProvider != handler {
330+
if !reflect.DeepEqual(e.defaultProviderReference.featureProvider, handler) {
331331
return
332332
}
333333

openfeature/event_executor_test.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func TestEventHandler_Eventing(t *testing.T) {
8282
rsp <- details
8383
}
8484

85-
AddHandler(ProviderReady, &callBack)
85+
eventType := ProviderConfigChange
86+
AddHandler(eventType, &callBack)
8687

8788
fCh := []string{"flagA"}
8889
meta := map[string]interface{}{
@@ -91,7 +92,7 @@ func TestEventHandler_Eventing(t *testing.T) {
9192

9293
// trigger event from provider implementation
9394
eventingImpl.Invoke(Event{
94-
EventType: ProviderReady,
95+
EventType: eventType,
9596
ProviderEventDetails: ProviderEventDetails{
9697
Message: "ReadyMessage",
9798
FlagChanges: fCh,
@@ -139,7 +140,7 @@ func TestEventHandler_Eventing(t *testing.T) {
139140
// associated to client name
140141
associatedName := "providerForClient"
141142

142-
err := SetNamedProvider(associatedName, eventingProvider)
143+
err := SetNamedProviderAndWait(associatedName, eventingProvider)
143144
if err != nil {
144145
t.Fatal(err)
145146
}
@@ -214,13 +215,13 @@ func TestEventHandler_clientAssociation(t *testing.T) {
214215
}
215216

216217
// default provider
217-
err := SetProvider(defaultProvider)
218+
err := SetProviderAndWait(defaultProvider)
218219
if err != nil {
219220
t.Fatal(err)
220221
}
221222

222223
// named provider(associated to name someClient)
223-
err = SetNamedProvider("someClient", struct {
224+
err = SetNamedProviderAndWait("someClient", struct {
224225
FeatureProvider
225226
EventHandler
226227
}{
@@ -273,7 +274,7 @@ func TestEventHandler_ErrorHandling(t *testing.T) {
273274
eventing,
274275
}
275276

276-
errorCallback := func(e EventDetails) {
277+
failingCallback := func(e EventDetails) {
277278
panic("callback panic")
278279
}
279280

@@ -292,24 +293,26 @@ func TestEventHandler_ErrorHandling(t *testing.T) {
292293
t.Fatal(err)
293294
}
294295

296+
successEventType := ProviderStale
297+
295298
// api level handlers
296-
AddHandler(ProviderReady, &errorCallback)
297-
AddHandler(ProviderReady, &successAPICallback)
299+
AddHandler(ProviderConfigChange, &failingCallback)
300+
AddHandler(successEventType, &successAPICallback)
298301

299302
// provider association
300303
providerName := "providerA"
301304

302305
client := NewClient(providerName)
303306

304307
// client level handlers
305-
client.AddHandler(ProviderReady, &errorCallback)
306-
client.AddHandler(ProviderReady, &successClientCallback)
308+
client.AddHandler(ProviderConfigChange, &failingCallback)
309+
client.AddHandler(successEventType, &successClientCallback)
307310

308311
// trigger events manually
309312
go func() {
310313
eventing.Invoke(Event{
311314
ProviderName: providerName,
312-
EventType: ProviderReady,
315+
EventType: successEventType,
313316
ProviderEventDetails: ProviderEventDetails{},
314317
})
315318
}()
@@ -967,7 +970,7 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) {
967970
},
968971
}
969972

970-
if err := SetProvider(provider); err != nil {
973+
if err := SetProviderAndWait(provider); err != nil {
971974
t.Fatal(err)
972975
}
973976

@@ -1001,7 +1004,7 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) {
10011004
},
10021005
}
10031006

1004-
if err := SetProvider(provider); err != nil {
1007+
if err := SetProviderAndWait(provider); err != nil {
10051008
t.Fatal(err)
10061009
}
10071010

openfeature/openfeature.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,25 @@ func initSingleton() {
2020
// SetProvider sets the default provider. Provider initialization is asynchronous and status can be checked from
2121
// provider status
2222
func SetProvider(provider FeatureProvider) error {
23-
return api.setProvider(provider)
23+
return api.setProvider(provider, true)
24+
}
25+
26+
// SetProviderAndWait sets the default provider and waits for its initialization.
27+
// Returns an error if initialization cause error
28+
func SetProviderAndWait(provider FeatureProvider) error {
29+
return api.setProvider(provider, false)
2430
}
2531

2632
// SetNamedProvider sets a provider mapped to the given Client name. Provider initialization is asynchronous and
2733
// status can be checked from provider status
2834
func SetNamedProvider(clientName string, provider FeatureProvider) error {
29-
return api.setNamedProvider(clientName, provider)
35+
return api.setNamedProvider(clientName, provider, true)
36+
}
37+
38+
// SetNamedProviderAndWait sets a provider mapped to the given Client name and waits for its initialization.
39+
// Returns an error if initialization cause error
40+
func SetNamedProviderAndWait(clientName string, provider FeatureProvider) error {
41+
return api.setNamedProvider(clientName, provider, false)
3042
}
3143

3244
// SetEvaluationContext sets the global evaluation context.

0 commit comments

Comments
 (0)