diff --git a/openfeature/event_executor.go b/openfeature/event_executor.go index 01fa1718..9673fc1a 100644 --- a/openfeature/event_executor.go +++ b/openfeature/event_executor.go @@ -1,12 +1,10 @@ package openfeature import ( - "fmt" "log/slog" "maps" "slices" "sync" - "time" ) const defaultDomain = "" @@ -192,7 +190,7 @@ func (e *eventExecutor) State(domain string) State { } // registerDefaultProvider registers the default FeatureProvider and remove the old default provider if available -func (e *eventExecutor) registerDefaultProvider(provider FeatureProvider) error { +func (e *eventExecutor) registerDefaultProvider(provider FeatureProvider) { e.mu.Lock() defer e.mu.Unlock() @@ -200,11 +198,11 @@ func (e *eventExecutor) registerDefaultProvider(provider FeatureProvider) error oldProvider := e.defaultProviderReference e.defaultProviderReference = newProvider - return e.startListeningAndShutdownOld(newProvider, oldProvider) + e.startListeningAndShutdownOld(newProvider, oldProvider) } // registerNamedEventingProvider registers a named FeatureProvider and remove event listener for old named provider -func (e *eventExecutor) registerNamedEventingProvider(associatedClient string, provider FeatureProvider) error { +func (e *eventExecutor) registerNamedEventingProvider(associatedClient string, provider FeatureProvider) { e.mu.Lock() defer e.mu.Unlock() newProvider := newProviderRef(provider) @@ -212,12 +210,12 @@ func (e *eventExecutor) registerNamedEventingProvider(associatedClient string, p oldProvider := e.namedProviderReference[associatedClient] e.namedProviderReference[associatedClient] = newProvider - return e.startListeningAndShutdownOld(newProvider, oldProvider) + e.startListeningAndShutdownOld(newProvider, oldProvider) } // startListeningAndShutdownOld is a helper to start concurrent listening to new provider events and invoke shutdown // hook of the old provider if it's not bound by another subscription -func (e *eventExecutor) startListeningAndShutdownOld(newProvider providerReference, oldReference providerReference) error { +func (e *eventExecutor) startListeningAndShutdownOld(newProvider providerReference, oldReference providerReference) { // check if this provider already actively handled - 1:N binding capability if !isRunning(newProvider, e.activeSubscriptions) { e.activeSubscriptions = append(e.activeSubscriptions, newProvider) @@ -247,7 +245,7 @@ func (e *eventExecutor) startListeningAndShutdownOld(newProvider providerReferen // check if this provider is still bound - 1:N binding capability if isBound(oldReference, e.defaultProviderReference, slices.Collect(maps.Values(e.namedProviderReference))) { - return nil + return } // drop from active references @@ -258,16 +256,28 @@ func (e *eventExecutor) startListeningAndShutdownOld(newProvider providerReferen _, ok := oldReference.featureProvider.(EventHandler) if !ok { // no shutdown for non event handling provider - return nil + return } // avoid shutdown lockouts select { case oldReference.shutdownSemaphore <- "": - return nil - case <-time.After(200 * time.Millisecond): - return fmt.Errorf("old event handler %s timeout waiting for handler shutdown", - oldReference.featureProvider.Metadata().Name) + default: + // This should never happen: + // + // providerReference.shutdownSemaphore is created with + // a buffer size of 1, so it should allow sending at + // least one shutdown signal without blocking. Locking + // should prevent us from sending more than one + // signal. + // + // In the unlikely case that it does not, this + // shouldn't be a big deal: we have already swapped + // references in eventExecutor and openfeatureAPI, and + // the handler should be able to receive at least one + // shutdown signal later. + slog.Info("OF BUG: failed to send shutdown to old event handler", + "provider", oldReference.featureProvider.Metadata().Name) } } diff --git a/openfeature/event_executor_test.go b/openfeature/event_executor_test.go index 3e2a2030..a86e8386 100644 --- a/openfeature/event_executor_test.go +++ b/openfeature/event_executor_test.go @@ -30,19 +30,13 @@ func TestEventHandler_RegisterUnregisterEventProvider(t *testing.T) { } executor := newEventExecutor() - err := executor.registerDefaultProvider(eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerDefaultProvider(eventingProvider) if executor.defaultProviderReference.featureProvider != eventingProvider { t.Error("implementation should register default eventing provider") } - err = executor.registerNamedEventingProvider("domain", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("domain", eventingProvider) if _, ok := executor.namedProviderReference["domain"]; !ok { t.Errorf("implementation should register named eventing provider") @@ -444,7 +438,6 @@ func TestEventHandler_InitOfProvider(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderConfigChange}) provider := struct { FeatureProvider @@ -463,11 +456,13 @@ func TestEventHandler_InitOfProvider(t *testing.T) { client := NewClient("someClient") client.AddHandler(ProviderReady, &callback) - err := SetNamedProvider("providerWithoutClient", provider) + err := SetNamedProviderAndWait("providerWithoutClient", provider) if err != nil { t.Fatal(err) } + eventingImpl.Invoke(Event{EventType: ProviderConfigChange}) + select { case <-rsp: t.Errorf("event must have not emitted") @@ -482,17 +477,18 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { t.Run("for default provider in global scope", func(t *testing.T) { t.Cleanup(initSingleton) - eventingImpl := &ProviderEventing{ - c: make(chan Event, 1), - } - eventingImpl.Invoke(Event{EventType: ProviderError}) - - provider := struct { + initFailingProvider := struct { FeatureProvider + StateHandler EventHandler }{ NoopProvider{}, - eventingImpl, + &stateHandlerForTests{ + initF: func(e EvaluationContext) error { + return errors.New("init failed") + }, + }, + &ProviderEventing{c: make(chan Event, 1)}, } // callback @@ -502,10 +498,7 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { } AddHandler(ProviderError, &callback) - err := SetProvider(provider) - if err != nil { - t.Fatal(err) - } + _ = SetProviderAndWait(initFailingProvider) // init fails; we only care that ProviderError handler runs select { case <-rsp: @@ -518,17 +511,18 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { t.Run("for default provider with unassociated client handler", func(t *testing.T) { t.Cleanup(initSingleton) - eventingImpl := &ProviderEventing{ - c: make(chan Event, 1), - } - eventingImpl.Invoke(Event{EventType: ProviderError}) - - provider := struct { + initFailingProvider := struct { FeatureProvider + StateHandler EventHandler }{ NoopProvider{}, - eventingImpl, + &stateHandlerForTests{ + initF: func(e EvaluationContext) error { + return errors.New("init failed") + }, + }, + &ProviderEventing{c: make(chan Event, 1)}, } // callback @@ -540,10 +534,7 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { client := NewClient("clientWithNoProviderAssociation") client.AddHandler(ProviderError, &callback) - err := SetProvider(provider) - if err != nil { - t.Fatal(err) - } + _ = SetProviderAndWait(initFailingProvider) // init fails; we only care that ProviderError handler runs select { case <-rsp: @@ -556,17 +547,18 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { t.Run("for named provider in client scope", func(t *testing.T) { t.Cleanup(initSingleton) - eventingImpl := &ProviderEventing{ - c: make(chan Event, 1), - } - eventingImpl.Invoke(Event{EventType: ProviderError}) - - provider := struct { + initFailingProvider := struct { FeatureProvider + StateHandler EventHandler }{ NoopProvider{}, - eventingImpl, + &stateHandlerForTests{ + initF: func(e EvaluationContext) error { + return errors.New("init failed") + }, + }, + &ProviderEventing{c: make(chan Event, 1)}, } // callback @@ -578,10 +570,7 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { client := NewClient("someClient") client.AddHandler(ProviderError, &callback) - err := SetNamedProvider("someClient", provider) - if err != nil { - t.Fatal(err) - } + _ = SetNamedProviderAndWait("someClient", initFailingProvider) // init fails; we only care that ProviderError handler runs select { case <-rsp: @@ -597,7 +586,6 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderError}) provider := struct { FeatureProvider @@ -616,11 +604,13 @@ func TestEventHandler_InitOfProviderError(t *testing.T) { client := NewClient("provider") client.AddHandler(ProviderError, &callback) - err := SetNamedProvider("someClient", provider) + err := SetNamedProviderAndWait("someClient", provider) if err != nil { t.Fatal(err) } + eventingImpl.Invoke(Event{EventType: ProviderError}) + select { case <-rsp: t.Errorf("event must have not emitted") @@ -860,7 +850,6 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderError}) provider := struct { FeatureProvider @@ -870,10 +859,17 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl, } - if err := SetProvider(provider); err != nil { + if err := SetProviderAndWait(provider); err != nil { t.Fatal(err) } + // Emit error event and wait for state transition before registering handler, + // so that AddHandler fires via emitOnRegistration (spec 5.3.3). + eventingImpl.Invoke(Event{EventType: ProviderError}) + eventually(t, func() bool { + return NewDefaultClient().State() == ErrorState + }, time.Second, time.Millisecond*100, "provider did not transition to ERROR state") + rsp := make(chan EventDetails, 1) callback := func(e EventDetails) { rsp <- e @@ -895,7 +891,6 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderStale}) provider := struct { FeatureProvider @@ -905,10 +900,17 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl, } - if err := SetProvider(provider); err != nil { + if err := SetProviderAndWait(provider); err != nil { t.Fatal(err) } + // Emit stale event and wait for state transition before registering handler, + // so that AddHandler fires via emitOnRegistration (spec 5.3.3). + eventingImpl.Invoke(Event{EventType: ProviderStale}) + eventually(t, func() bool { + return NewDefaultClient().State() == StaleState + }, time.Second, time.Millisecond*100, "provider did not transition to STALE state") + rsp := make(chan EventDetails, 1) callback := func(e EventDetails) { rsp <- e @@ -966,7 +968,6 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderError}) provider := struct { FeatureProvider @@ -990,6 +991,9 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { AddHandler(ProviderStale, &callback) AddHandler(ProviderConfigChange, &callback) + // Emit error event from the provider + eventingImpl.Invoke(Event{EventType: ProviderError}) + // assert client transitioned to ERROR eventually(t, func() bool { return NewDefaultClient().State() == ErrorState @@ -1009,7 +1013,6 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { eventingImpl := &ProviderEventing{ c: make(chan Event, 1), } - eventingImpl.Invoke(Event{EventType: ProviderStale}) provider := struct { FeatureProvider @@ -1033,6 +1036,9 @@ func TestEventHandler_HandlersRunImmediately(t *testing.T) { AddHandler(ProviderError, &callback) AddHandler(ProviderConfigChange, &callback) + // Emit stale event from the provider. + eventingImpl.Invoke(Event{EventType: ProviderStale}) + // assert client transitioned to STALE eventually(t, func() bool { return NewDefaultClient().State() == StaleState @@ -1198,25 +1204,16 @@ func TestEventHandler_1ToNMapping(t *testing.T) { executor := newEventExecutor() - err := executor.registerDefaultProvider(eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerDefaultProvider(eventingProvider) if len(executor.activeSubscriptions) != 1 && executor.activeSubscriptions[0].featureProvider != eventingProvider.FeatureProvider { t.Fatal("provider not added to active provider subscriptions") } - err = executor.registerNamedEventingProvider("clientA", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", eventingProvider) - err = executor.registerNamedEventingProvider("clientB", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientB", eventingProvider) if len(executor.activeSubscriptions) != 1 { t.Fatal("multiple provided in active subscriptions") @@ -1236,15 +1233,9 @@ func TestEventHandler_1ToNMapping(t *testing.T) { executor := newEventExecutor() - err := executor.registerDefaultProvider(eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerDefaultProvider(eventingProvider) - err = executor.registerNamedEventingProvider("clientA", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", eventingProvider) overridingProvider := struct { FeatureProvider @@ -1256,10 +1247,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - err = executor.registerNamedEventingProvider("clientA", overridingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", overridingProvider) if len(executor.activeSubscriptions) != 2 { t.Fatal("error with active provider subscriptions") @@ -1279,15 +1267,9 @@ func TestEventHandler_1ToNMapping(t *testing.T) { executor := newEventExecutor() - err := executor.registerNamedEventingProvider("clientA", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", eventingProvider) - err = executor.registerNamedEventingProvider("clientB", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientB", eventingProvider) overridingProvider := struct { FeatureProvider @@ -1299,10 +1281,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - err = executor.registerNamedEventingProvider("clientA", overridingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", overridingProvider) if len(executor.activeSubscriptions) != 2 { t.Fatal("error with active provider subscriptions") @@ -1322,10 +1301,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { executor := newEventExecutor() - err := executor.registerNamedEventingProvider("clientA", eventingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", eventingProvider) overridingProvider := struct { FeatureProvider @@ -1337,10 +1313,7 @@ func TestEventHandler_1ToNMapping(t *testing.T) { }, } - err = executor.registerNamedEventingProvider("clientA", overridingProvider) - if err != nil { - t.Fatal(err) - } + executor.registerNamedEventingProvider("clientA", overridingProvider) if len(executor.activeSubscriptions) != 1 && executor.activeSubscriptions[0].featureProvider != overridingProvider.FeatureProvider { @@ -1560,8 +1533,7 @@ func TestEventHandler_ChannelClosure(t *testing.T) { executor := newEventExecutor() - err := executor.registerDefaultProvider(eventingProvider) - require.NoError(t, err) + executor.registerDefaultProvider(eventingProvider) require.Len(t, executor.activeSubscriptions, 1) var eventCount atomic.Int64 diff --git a/openfeature/openfeature_api.go b/openfeature/openfeature_api.go index 715a4ca0..39a12ff8 100644 --- a/openfeature/openfeature_api.go +++ b/openfeature/openfeature_api.go @@ -34,163 +34,120 @@ func newEvaluationAPI(eventExecutor *eventExecutor) *evaluationAPI { } func (api *evaluationAPI) SetProvider(provider FeatureProvider) error { - return api.setProvider(provider, true) + return api.SetProviderWithContext(context.Background(), provider) } func (api *evaluationAPI) SetProviderAndWait(provider FeatureProvider) error { - return api.setProvider(provider, false) + return api.SetProviderWithContextAndWait(context.Background(), provider) } -// GetProviderMetadata returns the default FeatureProvider's metadata -func (api *evaluationAPI) GetProviderMetadata() Metadata { - api.mu.RLock() - defer api.mu.RUnlock() +// SetProviderWithContext sets the default FeatureProvider with context-aware initialization. +func (api *evaluationAPI) SetProviderWithContext(ctx context.Context, provider FeatureProvider) error { + if provider == nil { + return errors.New("default provider cannot be set to nil") + } + api.setProviderWithContext(ctx, provider) + return nil +} - return api.defaultProvider.Metadata() +// SetProviderWithContextAndWait sets the default FeatureProvider with context-aware initialization and waits for completion. +func (api *evaluationAPI) SetProviderWithContextAndWait(ctx context.Context, provider FeatureProvider) error { + if provider == nil { + return errors.New("default provider cannot be set to nil") + } + initCh := api.setProviderWithContext(ctx, provider) + return <-initCh } // SetNamedProvider sets a provider with client name. Returns an error if FeatureProvider is nil func (api *evaluationAPI) SetNamedProvider(clientName string, provider FeatureProvider, async bool) error { - api.mu.Lock() - defer api.mu.Unlock() + return api.SetNamedProviderWithContext(context.Background(), clientName, provider, async) +} +// SetNamedProviderWithContext sets a provider with client name using context-aware initialization. +func (api *evaluationAPI) SetNamedProviderWithContext(ctx context.Context, clientName string, provider FeatureProvider, async bool) error { if provider == nil { return errors.New("provider cannot be set to nil") } - // Initialize new named provider and Shutdown the old one - // Provider update must be non-blocking, hence initialization & Shutdown happens concurrently - oldProvider := api.namedProviders[clientName] - api.namedProviders[clientName] = provider + initCh := api.setNamedProviderWithContext(ctx, clientName, provider) - err := api.initNewAndShutdownOld(context.Background(), clientName, provider, oldProvider, async) - if err != nil { - return err - } - - err = api.eventExecutor.registerNamedEventingProvider(clientName, provider) - if err != nil { - return err - } - - return nil -} - -// GetNamedProviderMetadata returns the default FeatureProvider's metadata -func (api *evaluationAPI) GetNamedProviderMetadata(name string) Metadata { - api.mu.RLock() - defer api.mu.RUnlock() - - provider, ok := api.namedProviders[name] - if !ok { - return ProviderMetadata() + if async { + return nil } - return provider.Metadata() -} - -// Context-aware provider setup methods - -// SetProviderWithContext sets the default FeatureProvider with context-aware initialization. -func (api *evaluationAPI) SetProviderWithContext(ctx context.Context, provider FeatureProvider) error { - return api.setProviderWithContext(ctx, provider, true) + return <-initCh } -// SetProviderWithContextAndWait sets the default FeatureProvider with context-aware initialization and waits for completion. -func (api *evaluationAPI) SetProviderWithContextAndWait(ctx context.Context, provider FeatureProvider) error { - return api.setProviderWithContext(ctx, provider, false) +// SetNamedProviderWithContextAndWait sets a provider with client name using context-aware initialization and waits for completion. +func (api *evaluationAPI) SetNamedProviderWithContextAndWait(ctx context.Context, clientName string, provider FeatureProvider) error { + return api.SetNamedProviderWithContext(ctx, clientName, provider, false) } // setProviderWithContext sets the default FeatureProvider of the evaluationAPI with context-aware initialization. -func (api *evaluationAPI) setProviderWithContext(ctx context.Context, provider FeatureProvider, async bool) error { +func (api *evaluationAPI) setProviderWithContext(ctx context.Context, provider FeatureProvider) <-chan error { api.mu.Lock() defer api.mu.Unlock() - if provider == nil { - return errors.New("default provider cannot be set to nil") - } - oldProvider := api.defaultProvider api.defaultProvider = provider - err := api.initNewAndShutdownOld(ctx, "", provider, oldProvider, async) - if err != nil { - return fmt.Errorf("failed to initialize default provider %q: %w", provider.Metadata().Name, err) - } + api.eventExecutor.registerDefaultProvider(provider) - err = api.eventExecutor.registerDefaultProvider(provider) - if err != nil { - return fmt.Errorf("failed to register default provider %q: %w", provider.Metadata().Name, err) - } + api.shutdownOld(ctx, oldProvider) - return nil + return api.initNew(ctx, "", provider) } -// SetNamedProviderWithContext sets a provider with client name using context-aware initialization. -func (api *evaluationAPI) SetNamedProviderWithContext(ctx context.Context, clientName string, provider FeatureProvider, async bool) error { +func (api *evaluationAPI) setNamedProviderWithContext(ctx context.Context, clientName string, provider FeatureProvider) <-chan error { api.mu.Lock() defer api.mu.Unlock() - if provider == nil { - return errors.New("provider cannot be set to nil") - } - // Initialize new named provider and Shutdown the old one oldProvider := api.namedProviders[clientName] api.namedProviders[clientName] = provider - err := api.initNewAndShutdownOld(ctx, clientName, provider, oldProvider, async) - if err != nil { - return fmt.Errorf("failed to initialize named provider %q for domain %q: %w", provider.Metadata().Name, clientName, err) - } + api.eventExecutor.registerNamedEventingProvider(clientName, provider) - err = api.eventExecutor.registerNamedEventingProvider(clientName, provider) - if err != nil { - return fmt.Errorf("failed to register named provider %q for domain %q: %w", provider.Metadata().Name, clientName, err) - } + api.shutdownOld(ctx, oldProvider) - return nil + return api.initNew(ctx, clientName, provider) } -// SetNamedProviderWithContextAndWait sets a provider with client name using context-aware initialization and waits for completion. -func (api *evaluationAPI) SetNamedProviderWithContextAndWait(ctx context.Context, clientName string, provider FeatureProvider) error { - return api.SetNamedProviderWithContext(ctx, clientName, provider, false) -} +func (api *evaluationAPI) initNew(ctx context.Context, clientName string, newProvider FeatureProvider) <-chan error { + errCh := make(chan error, 1) + + // Initialize new provider async. The caller may wait on the channel. + go func(executor *eventExecutor, evalCtx EvaluationContext, ctx context.Context, provider FeatureProvider, clientName string) { + event, err := initializerWithContext(ctx, provider, evalCtx) + executor.triggerEvent(event, provider) -// initNewAndShutdownOld is the main helper to initialise new FeatureProvider and Shutdown the old FeatureProvider. -// Always uses the context-aware initializer with the provided context. -// -// When shutting down old providers that implement ContextAwareStateHandler, a 10-second timeout -// is applied to prevent hanging if the provider becomes unresponsive during shutdown. -func (api *evaluationAPI) initNewAndShutdownOld(ctx context.Context, clientName string, newProvider FeatureProvider, oldProvider FeatureProvider, async bool) error { - if async { - go func(executor *eventExecutor, evalCtx EvaluationContext, ctx context.Context, provider FeatureProvider, clientName string) { - // for async initialization, error is conveyed as an event - event, _ := initializerWithContext(ctx, provider, evalCtx) - executor.states.Store(clientName, stateFromEventOrError(event, nil)) - executor.triggerEvent(event, provider) - }(api.eventExecutor, api.evalCtx, ctx, newProvider, clientName) - } else { - event, err := initializerWithContext(ctx, newProvider, api.evalCtx) - api.eventExecutor.states.Store(clientName, stateFromEventOrError(event, err)) - api.eventExecutor.triggerEvent(event, newProvider) if err != nil { - return err + if clientName == "" { + err = fmt.Errorf("failed to initialize default provider %q: %w", provider.Metadata().Name, err) + } else { + err = fmt.Errorf("failed to initialize named provider %q for domain %q: %w", provider.Metadata().Name, clientName, err) + } } - } + errCh <- err + }(api.eventExecutor, api.evalCtx, ctx, newProvider, clientName) + + return errCh +} +func (api *evaluationAPI) shutdownOld(ctx context.Context, oldProvider FeatureProvider) { v, ok := oldProvider.(StateHandler) // oldProvider can be nil or without state handling capability if oldProvider == nil || !ok { - return nil + return } namedProviders := slices.Collect(maps.Values(api.namedProviders)) // check for multiple bindings if oldProvider == api.defaultProvider || slices.Contains(namedProviders, oldProvider) { - return nil + return } go func(forShutdown StateHandler, parentCtx context.Context) { @@ -203,8 +160,27 @@ func (api *evaluationAPI) initNewAndShutdownOld(ctx context.Context, clientName forShutdown.Shutdown() } }(v, ctx) +} - return nil +// GetProviderMetadata returns the default FeatureProvider's metadata +func (api *evaluationAPI) GetProviderMetadata() Metadata { + api.mu.RLock() + defer api.mu.RUnlock() + + return api.defaultProvider.Metadata() +} + +// GetNamedProviderMetadata returns the default FeatureProvider's metadata +func (api *evaluationAPI) GetNamedProviderMetadata(name string) Metadata { + api.mu.RLock() + defer api.mu.RUnlock() + + provider, ok := api.namedProviders[name] + if !ok { + return ProviderMetadata() + } + + return provider.Metadata() } // GetNamedProviders returns named providers map. @@ -324,32 +300,6 @@ func (api *evaluationAPI) GetProvider() FeatureProvider { return api.defaultProvider } -// SetProvider sets the default FeatureProvider of the evaluationAPI. -// Returns an error if provider registration cause an error -func (api *evaluationAPI) setProvider(provider FeatureProvider, async bool) error { - api.mu.Lock() - defer api.mu.Unlock() - - if provider == nil { - return errors.New("default provider cannot be set to nil") - } - - oldProvider := api.defaultProvider - api.defaultProvider = provider - - err := api.initNewAndShutdownOld(context.Background(), "", provider, oldProvider, async) - if err != nil { - return err - } - - err = api.eventExecutor.registerDefaultProvider(provider) - if err != nil { - return err - } - - return nil -} - // initializerWithContext is a context-aware helper to execute provider initialization and generate appropriate event for the initialization // If the provider implements ContextAwareStateHandler, InitWithContext is called; otherwise, Init is called for backward compatibility. // It also returns an error if the initialization resulted in an error or if the context is cancelled. @@ -418,27 +368,9 @@ var statesMap = map[EventType]func(ProviderEventDetails) State{ }, } -func stateFromEventOrError(event Event, err error) State { - if err != nil { - return stateFromError(err) - } - return stateFromEvent(event) -} - func stateFromEvent(event Event) State { if stateFn, ok := statesMap[event.EventType]; ok { return stateFn(event.ProviderEventDetails) } return NotReadyState // default } - -func stateFromError(err error) State { - var e *ProviderInitError - switch { - case errors.As(err, &e): - if e.ErrorCode == ProviderFatalCode { - return FatalState - } - } - return ErrorState // default -} diff --git a/openfeature/reference.go b/openfeature/reference.go index ab04ca35..18c7da6b 100644 --- a/openfeature/reference.go +++ b/openfeature/reference.go @@ -9,7 +9,7 @@ func newProviderRef(provider FeatureProvider) providerReference { return providerReference{ featureProvider: provider, kind: reflect.TypeOf(provider).Kind(), - shutdownSemaphore: make(chan any), + shutdownSemaphore: make(chan any, 1), } }