Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provider client 1:n binding support #190

Merged
merged 21 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.7
github.com/open-feature/go-sdk-contrib/tests/flagd v1.1.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/text v0.11.0
golang.org/x/text v0.10.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
81 changes: 61 additions & 20 deletions pkg/openfeature/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/go-logr/logr"
"github.com/open-feature/go-sdk/pkg/openfeature/internal"
"golang.org/x/exp/maps"
)

// evaluationAPI wraps OpenFeature evaluation API functionalities
Expand All @@ -16,7 +17,7 @@ type evaluationAPI struct {
evalCtx EvaluationContext
logger logr.Logger
mu sync.RWMutex
eventExecutor
eventExecutor *eventExecutor
}

// newEvaluationAPI is a helper to generate an API. Used internally
Expand Down Expand Up @@ -46,10 +47,10 @@ func (api *evaluationAPI) setProvider(provider FeatureProvider) error {
// Initialize new default provider and shutdown the old one
// Provider update must be non-blocking, hence initialization & shutdown happens concurrently
oldProvider := api.defaultProvider
api.initAndShutdown(provider, oldProvider)
api.defaultProvider = provider

err := api.registerDefaultProvider(provider)
api.initNewAndShutdownOld(provider, oldProvider)
err := api.eventExecutor.registerDefaultProvider(provider)
if err != nil {
return err
}
Expand All @@ -76,10 +77,11 @@ func (api *evaluationAPI) setNamedProvider(clientName string, provider FeaturePr

// Initialize new default provider and shutdown the old one
// Provider update must be non-blocking, hence initialization & shutdown happens concurrently
api.initAndShutdown(provider, api.namedProviders[clientName])
oldProvider := api.namedProviders[clientName]
api.namedProviders[clientName] = provider

err := api.registerNamedEventingProvider(clientName, provider)
api.initNewAndShutdownOld(provider, oldProvider)
err := api.eventExecutor.registerNamedEventingProvider(clientName, provider)
if err != nil {
return err
}
Expand Down Expand Up @@ -125,6 +127,9 @@ func (api *evaluationAPI) addHooks(hooks ...Hook) {
}

func (api *evaluationAPI) shutdown() {
api.mu.Lock()
defer api.mu.Unlock()

v, ok := api.defaultProvider.(StateHandler)
if ok {
v.Shutdown()
Expand Down Expand Up @@ -161,21 +166,57 @@ func (api *evaluationAPI) forTransaction(clientName string) (FeatureProvider, []
return provider, api.hks, api.evalCtx
}

// initAndShutdown is a helper to initialise new FeatureProvider and shutdown old FeatureProvider.
// Operation happens concurrently.
func (api *evaluationAPI) initAndShutdown(newProvider FeatureProvider, oldProvider FeatureProvider) {
go func() {
v, ok := newProvider.(StateHandler)
if ok {
v.Init(api.evalCtx)
}

// oldProvider can be nil
if oldProvider != nil {
v, ok = oldProvider.(StateHandler)
if ok {
v.Shutdown()
// initNewAndShutdownOld is a helper to initialise new FeatureProvider and shutdown the old FeatureProvider.
// Operations happen concurrently.
func (api *evaluationAPI) initNewAndShutdownOld(newProvider FeatureProvider, oldProvider FeatureProvider) {
v, ok := newProvider.(StateHandler)
if ok && v.Status() == NotReadyState {
go func(provider FeatureProvider, stateHandler StateHandler, evalCtx EvaluationContext, eventChan chan eventPayload) {
err := stateHandler.Init(evalCtx)
// emit ready/error event once initialization is complete
if err != nil {
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
eventChan <- eventPayload{
Event{
ProviderName: provider.Metadata().Name,
EventType: ProviderError,
ProviderEventDetails: ProviderEventDetails{},
}, provider,
}
} else {
eventChan <- eventPayload{
Event{
ProviderName: provider.Metadata().Name,
EventType: ProviderReady,
ProviderEventDetails: ProviderEventDetails{},
}, provider,
}
}
}(newProvider, v, api.evalCtx, api.eventExecutor.eventChan)
}

v, ok = oldProvider.(StateHandler)

// oldProvider can be nil or without state handling capability
if oldProvider == nil || !ok {
return
}

// check for multiple bindings
if oldProvider == api.defaultProvider || contains(oldProvider, maps.Values(api.namedProviders)) {
return
}

go func(forShutdown StateHandler) {
Kavindu-Dodan marked this conversation as resolved.
Show resolved Hide resolved
forShutdown.Shutdown()
}(v)
}

func contains(provider FeatureProvider, in []FeatureProvider) bool {
for _, p := range in {
if provider == p {
return true
}
}()
}

return false
}
Loading