Skip to content

Commit 32e4fc0

Browse files
feat(lib): set Offline flag for ff client (thomaspoignant#1774)
* Feature to set Offline flag for ff client Why: To optimize the ff client How: Added a new function named SetOffline, and that takes boolean as a parameter, and returns the updated value. It will start or stop the flag updater daemon based on the input. The background_updater is also updated to check for close() signals. Tags: [ffclient, enhancement, feature] * Added sync.Once * fix: race condition Signed-off-by: Thomas Poignant <[email protected]> * fix data race Signed-off-by: Thomas Poignant <[email protected]> * remove unused checks Signed-off-by: Thomas Poignant <[email protected]> --------- Signed-off-by: Thomas Poignant <[email protected]> Co-authored-by: Thomas Poignant <[email protected]>
1 parent e8e4e66 commit 32e4fc0

File tree

4 files changed

+85
-13
lines changed

4 files changed

+85
-13
lines changed

background_updater.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func newBackgroundUpdater(pollingInterval time.Duration, useJitter bool) backgro
3535
}
3636
}
3737

38-
// close stop the ticker and close the channel.
38+
// close stops the ticker and closes the channel.
3939
func (bgu *backgroundUpdater) close() {
4040
bgu.ticker.Stop()
4141
close(bgu.updaterChan)

config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"log"
7+
"sync"
78
"time"
89

910
"github.com/thomaspoignant/go-feature-flag/retriever"
@@ -77,6 +78,9 @@ type Config struct {
7778
// if in the evaluation context you have a field with the same name, it will override the common one.
7879
// Default: nil
7980
EvaluationContextEnrichment map[string]interface{}
81+
82+
// offlineMutex is a mutex to protect the Offline field.
83+
offlineMutex *sync.RWMutex
8084
}
8185

8286
// GetRetrievers returns a retriever.Retriever configure with the retriever available in the config.
@@ -96,3 +100,23 @@ func (c *Config) GetRetrievers() ([]retriever.Retriever, error) {
96100
}
97101
return retrievers, nil
98102
}
103+
104+
// SetOffline set GO Feature Flag in offline mode.
105+
func (c *Config) SetOffline(control bool) {
106+
if c.offlineMutex == nil {
107+
c.offlineMutex = &sync.RWMutex{}
108+
}
109+
c.offlineMutex.Lock()
110+
defer c.offlineMutex.Unlock()
111+
c.Offline = control
112+
}
113+
114+
// IsOffline return if the GO Feature Flag is in offline mode.
115+
func (c *Config) IsOffline() bool {
116+
if c.offlineMutex == nil {
117+
c.offlineMutex = &sync.RWMutex{}
118+
}
119+
c.offlineMutex.RLock()
120+
defer c.offlineMutex.RUnlock()
121+
return c.Offline
122+
}

feature_flag.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type GoFeatureFlag struct {
5959
var ff *GoFeatureFlag
6060
var onceFF sync.Once
6161

62-
// New creates a new go-feature-flag instance that retrieve the config from a YAML file
62+
// New creates a new go-feature-flag instances that retrieve the config from a YAML file
6363
// and return everything you need to manage your flags.
6464
func New(config Config) (*GoFeatureFlag, error) {
6565
switch {
@@ -76,6 +76,10 @@ func New(config Config) (*GoFeatureFlag, error) {
7676
// do nothing
7777
}
7878

79+
if config.offlineMutex == nil {
80+
config.offlineMutex = &sync.RWMutex{}
81+
}
82+
7983
goFF := &GoFeatureFlag{
8084
config: config,
8185
}
@@ -145,9 +149,11 @@ func (g *GoFeatureFlag) startFlagUpdaterDaemon() {
145149
for {
146150
select {
147151
case <-g.bgUpdater.ticker.C:
148-
err := retrieveFlagsAndUpdateCache(g.config, g.cache, g.retrieverManager)
149-
if err != nil {
150-
fflog.Printf(g.config.Logger, "error while updating the cache: %v\n", err)
152+
if !g.IsOffline() {
153+
err := retrieveFlagsAndUpdateCache(g.config, g.cache, g.retrieverManager)
154+
if err != nil {
155+
fflog.Printf(g.config.Logger, "error while updating the cache: %v\n", err)
156+
}
151157
}
152158
case <-g.bgUpdater.updaterChan:
153159
return
@@ -231,12 +237,32 @@ func (g *GoFeatureFlag) GetCacheRefreshDate() time.Time {
231237
return g.cache.GetLatestUpdateDate()
232238
}
233239

240+
// SetOffline updates the config Offline parameter
241+
func (g *GoFeatureFlag) SetOffline(control bool) {
242+
g.config.SetOffline(control)
243+
}
244+
245+
// IsOffline allows knowing if the feature flag is in offline mode
246+
func (g *GoFeatureFlag) IsOffline() bool {
247+
return g.config.IsOffline()
248+
}
249+
250+
// SetOffline updates the config Offline parameter
251+
func SetOffline(control bool) {
252+
ff.SetOffline(control)
253+
}
254+
255+
// IsOffline allows knowing if the feature flag is in offline mode
256+
func IsOffline() bool {
257+
return ff.IsOffline()
258+
}
259+
234260
// GetCacheRefreshDate gives the date of the latest refresh of the cache
235261
func GetCacheRefreshDate() time.Time {
236262
return ff.GetCacheRefreshDate()
237263
}
238264

239-
// GetPollingInterval is the polling interval between 2 refresh of the cache
265+
// GetPollingInterval is the polling interval between 2 refreshes of the cache
240266
func (g *GoFeatureFlag) GetPollingInterval() int64 {
241267
return g.config.PollingInterval.Milliseconds()
242268
}

feature_flag_test.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@ package ffclient_test
22

33
import (
44
"errors"
5-
"github.com/thomaspoignant/go-feature-flag/ffcontext"
6-
"github.com/thomaspoignant/go-feature-flag/testutils/initializableretriever"
75
"log"
86
"os"
97
"testing"
108
"time"
119

10+
"github.com/aws/aws-sdk-go/aws"
11+
"github.com/stretchr/testify/assert"
12+
ffclient "github.com/thomaspoignant/go-feature-flag"
13+
"github.com/thomaspoignant/go-feature-flag/ffcontext"
1214
"github.com/thomaspoignant/go-feature-flag/internal/flag"
1315
"github.com/thomaspoignant/go-feature-flag/retriever"
14-
1516
"github.com/thomaspoignant/go-feature-flag/retriever/fileretriever"
1617
"github.com/thomaspoignant/go-feature-flag/retriever/s3retriever"
17-
18-
"github.com/aws/aws-sdk-go/aws"
19-
"github.com/stretchr/testify/assert"
20-
ffclient "github.com/thomaspoignant/go-feature-flag"
18+
"github.com/thomaspoignant/go-feature-flag/testutils/initializableretriever"
2119
"github.com/thomaspoignant/go-feature-flag/testutils/mock"
2220
)
2321

@@ -118,6 +116,11 @@ func TestValidUseCase(t *testing.T) {
118116

119117
allFlags := ffclient.AllFlagsState(user)
120118
assert.Equal(t, 2, len(allFlags.GetFlags()))
119+
120+
ffclient.SetOffline(true)
121+
assert.True(t, ffclient.IsOffline())
122+
ffclient.SetOffline(false)
123+
assert.False(t, ffclient.IsOffline())
121124
}
122125

123126
func TestAllFlagsFromCache(t *testing.T) {
@@ -466,6 +469,25 @@ func TestGoFeatureFlag_GetCacheRefreshDate(t *testing.T) {
466469
}
467470
}
468471

472+
func TestGoFeatureFlag_SetOffline(t *testing.T) {
473+
gffClient, err := ffclient.New(ffclient.Config{
474+
PollingInterval: 1 * time.Second,
475+
Retriever: &fileretriever.Retriever{Path: "testdata/flag-config.yaml"},
476+
Logger: log.New(os.Stdout, "", 0),
477+
Offline: false,
478+
})
479+
assert.NoError(t, err)
480+
defer gffClient.Close()
481+
482+
gffClient.SetOffline(true)
483+
assert.True(t, gffClient.IsOffline())
484+
485+
time.Sleep(2 * time.Second)
486+
487+
gffClient.SetOffline(false)
488+
assert.False(t, gffClient.IsOffline())
489+
}
490+
469491
func Test_GetPollingInterval(t *testing.T) {
470492
tests := []struct {
471493
name string

0 commit comments

Comments
 (0)