Skip to content

Commit c67e6c7

Browse files
Merge pull request #95 from optimizely/pawel/fix_sync_on_close
Pawel/fix sync on close
2 parents 3370772 + bf67802 commit c67e6c7

File tree

9 files changed

+143
-95
lines changed

9 files changed

+143
-95
lines changed

examples/main.go

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
package main
55

66
import (
7-
"context"
87
"fmt"
9-
"time"
10-
118
"github.com/optimizely/go-sdk/optimizely/client"
129
"github.com/optimizely/go-sdk/optimizely/entities"
1310
"github.com/optimizely/go-sdk/optimizely/logging"
@@ -40,40 +37,15 @@ func main() {
4037
fmt.Printf("Is feature enabled? %v\n", enabled)
4138

4239
fmt.Println()
43-
44-
/************* ClientWithOptions - custom context ********************/
45-
46-
optimizelyFactory = &client.OptimizelyFactory{
47-
SDKKey: "4SLpaJA1r1pgE6T2CoMs9q",
48-
}
49-
ctx := context.Background()
50-
ctx, cancelManager := context.WithCancel(ctx) // user can set up any context
51-
clientOptions := client.Options{
52-
Context: ctx,
53-
}
54-
55-
app, err = optimizelyFactory.ClientWithOptions(clientOptions)
56-
cancelManager() // user can cancel anytime
57-
58-
if err != nil {
59-
fmt.Printf("Error instantiating client: %s", err)
60-
return
61-
}
62-
63-
enabled, _ = app.IsFeatureEnabled("mutext_feat", user)
64-
fmt.Printf("Is feature enabled? %v\n", enabled)
65-
66-
time.Sleep(1000 * time.Millisecond)
40+
app.Close() // user can close dispatcher
6741
fmt.Println()
68-
6942
/************* Client ********************/
7043

7144
optimizelyFactory = &client.OptimizelyFactory{
7245
SDKKey: "4SLpaJA1r1pgE6T2CoMs9q",
7346
}
7447

7548
app, err = optimizelyFactory.Client()
76-
app.Close() // user can cancel anytime
7749

7850
if err != nil {
7951
fmt.Printf("Error instantiating client: %s", err)
@@ -82,5 +54,5 @@ func main() {
8254

8355
enabled, _ = app.IsFeatureEnabled("mutext_feat", user)
8456
fmt.Printf("Is feature enabled? %v\n", enabled)
85-
57+
app.Close() // user can close dispatcher
8658
}

optimizely/client/client.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818
package client
1919

2020
import (
21-
"context"
2221
"errors"
2322
"fmt"
2423
"runtime/debug"
2524
"strconv"
2625

27-
"github.com/optimizely/go-sdk/optimizely/event"
28-
2926
"github.com/optimizely/go-sdk/optimizely"
3027
"github.com/optimizely/go-sdk/optimizely/decision"
3128
"github.com/optimizely/go-sdk/optimizely/entities"
29+
"github.com/optimizely/go-sdk/optimizely/event"
3230
"github.com/optimizely/go-sdk/optimizely/logging"
31+
"github.com/optimizely/go-sdk/optimizely/utils"
3332
)
3433

3534
var logger = logging.GetLogger("Client")
@@ -41,7 +40,7 @@ type OptimizelyClient struct {
4140
eventProcessor event.Processor
4241
isValid bool
4342

44-
cancelFunc context.CancelFunc
43+
executionCtx utils.ExecutionCtx
4544
}
4645

4746
// IsFeatureEnabled returns true if the feature is enabled for the given user
@@ -279,5 +278,5 @@ func (o *OptimizelyClient) GetProjectConfig() (projectConfig optimizely.ProjectC
279278

280279
// Close closes the Optimizely instance and stops any ongoing tasks from its children components
281280
func (o *OptimizelyClient) Close() {
282-
o.cancelFunc()
281+
o.executionCtx.TerminateAndWait()
283282
}

optimizely/client/factory.go

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
package client
1919

2020
import (
21-
"context"
2221
"errors"
22+
"github.com/optimizely/go-sdk/optimizely/utils"
2323

2424
"github.com/optimizely/go-sdk/optimizely/event"
2525

@@ -32,7 +32,6 @@ import (
3232

3333
// Options are used to create an instance of the OptimizelyClient with custom configuration
3434
type Options struct {
35-
Context context.Context
3635
ProjectConfigManager optimizely.ProjectConfigManager
3736
DecisionService decision.Service
3837
EventProcessor event.Processor
@@ -76,19 +75,11 @@ func (f OptimizelyFactory) StaticClient() (*OptimizelyClient, error) {
7675

7776
// ClientWithOptions returns a client initialized with the given configuration options
7877
func (f OptimizelyFactory) ClientWithOptions(clientOptions Options) (*OptimizelyClient, error) {
79-
client := &OptimizelyClient{
80-
isValid: false,
81-
}
8278

83-
var ctx context.Context
84-
if clientOptions.Context != nil {
85-
ctx = clientOptions.Context
86-
} else {
87-
// if no context is provided, we create our own cancellable context and hand it over to the client so the client can shut down its child processes
88-
ctx = context.Background()
89-
var cancel context.CancelFunc
90-
ctx, cancel = context.WithCancel(ctx)
91-
client.cancelFunc = cancel
79+
executionCtx := utils.NewCancelableExecutionCtx()
80+
client := &OptimizelyClient{
81+
isValid: false,
82+
executionCtx: executionCtx,
9283
}
9384

9485
notificationCenter := notification.NewNotificationCenter()
@@ -100,7 +91,7 @@ func (f OptimizelyFactory) ClientWithOptions(clientOptions Options) (*Optimizely
10091
options := config.PollingProjectConfigManagerOptions{
10192
Datafile: f.Datafile,
10293
}
103-
client.configManager = config.NewPollingProjectConfigManagerWithOptions(ctx, f.SDKKey, options)
94+
client.configManager = config.NewPollingProjectConfigManagerWithOptions(executionCtx, f.SDKKey, options)
10495
case f.Datafile != nil:
10596
staticConfigManager, _ := config.NewStaticProjectConfigManagerFromPayload(f.Datafile)
10697
client.configManager = staticConfigManager
@@ -117,7 +108,7 @@ func (f OptimizelyFactory) ClientWithOptions(clientOptions Options) (*Optimizely
117108
if clientOptions.EventProcessor != nil {
118109
client.eventProcessor = clientOptions.EventProcessor
119110
} else {
120-
client.eventProcessor = event.NewEventProcessor(ctx, event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval)
111+
client.eventProcessor = event.NewEventProcessor(executionCtx, event.DefaultBatchSize, event.DefaultEventQueueSize, event.DefaultEventFlushInterval)
121112
}
122113

123114
client.isValid = true

optimizely/config/polling_manager.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
package config
1919

2020
import (
21-
"context"
2221
"fmt"
2322
"sync"
2423
"time"
2524

2625
"github.com/optimizely/go-sdk/optimizely"
2726
"github.com/optimizely/go-sdk/optimizely/config/datafileprojectconfig"
2827
"github.com/optimizely/go-sdk/optimizely/logging"
28+
"github.com/optimizely/go-sdk/optimizely/utils"
2929
)
3030

3131
const defaultPollingInterval = 5 * time.Minute // default to 5 minutes for polling
@@ -50,7 +50,7 @@ type PollingProjectConfigManager struct {
5050
configLock sync.RWMutex
5151
err error
5252

53-
ctx context.Context // context used for cancellation
53+
exeCtx utils.ExecutionCtx // context used for execution control
5454
}
5555

5656
func (cm *PollingProjectConfigManager) activate(initialPayload []byte, init bool) {
@@ -89,15 +89,15 @@ func (cm *PollingProjectConfigManager) activate(initialPayload []byte, init bool
8989
select {
9090
case <-t.C:
9191
update()
92-
case <-cm.ctx.Done():
92+
case <-cm.exeCtx.GetContext().Done():
9393
cmLogger.Debug("Polling Config Manager Stopped")
9494
return
9595
}
9696
}
9797
}
9898

9999
// NewPollingProjectConfigManagerWithOptions returns new instance of PollingProjectConfigManager with the given options
100-
func NewPollingProjectConfigManagerWithOptions(ctx context.Context, sdkKey string, options PollingProjectConfigManagerOptions) *PollingProjectConfigManager {
100+
func NewPollingProjectConfigManagerWithOptions(exeCtx utils.ExecutionCtx, sdkKey string, options PollingProjectConfigManagerOptions) *PollingProjectConfigManager {
101101

102102
var requester Requester
103103
if options.Requester != nil {
@@ -112,7 +112,7 @@ func NewPollingProjectConfigManagerWithOptions(ctx context.Context, sdkKey strin
112112
pollingInterval = defaultPollingInterval
113113
}
114114

115-
pollingProjectConfigManager := PollingProjectConfigManager{requester: requester, pollingInterval: pollingInterval, ctx: ctx}
115+
pollingProjectConfigManager := PollingProjectConfigManager{requester: requester, pollingInterval: pollingInterval, exeCtx: exeCtx}
116116

117117
pollingProjectConfigManager.activate(options.Datafile, true) // initial poll
118118

@@ -122,9 +122,9 @@ func NewPollingProjectConfigManagerWithOptions(ctx context.Context, sdkKey strin
122122
}
123123

124124
// NewPollingProjectConfigManager returns an instance of the polling config manager with the default configuration
125-
func NewPollingProjectConfigManager(ctx context.Context, sdkKey string) *PollingProjectConfigManager {
125+
func NewPollingProjectConfigManager(exeCtx utils.ExecutionCtx, sdkKey string) *PollingProjectConfigManager {
126126
options := PollingProjectConfigManagerOptions{}
127-
configManager := NewPollingProjectConfigManagerWithOptions(ctx, sdkKey, options)
127+
configManager := NewPollingProjectConfigManagerWithOptions(exeCtx, sdkKey, options)
128128
return configManager
129129
}
130130

optimizely/config/polling_manager_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package config
1818

1919
import (
20-
"context"
20+
"github.com/optimizely/go-sdk/optimizely/utils"
2121
"testing"
2222

2323
"github.com/stretchr/testify/assert"
@@ -47,7 +47,9 @@ func TestNewPollingProjectConfigManagerWithOptions(t *testing.T) {
4747
options := PollingProjectConfigManagerOptions{
4848
Requester: mockRequester,
4949
}
50-
configManager := NewPollingProjectConfigManagerWithOptions(context.Background(), sdkKey, options)
50+
51+
exeCtx := utils.NewCancelableExecutionCtx()
52+
configManager := NewPollingProjectConfigManagerWithOptions(exeCtx, sdkKey, options)
5153
mockRequester.AssertExpectations(t)
5254

5355
actual, err := configManager.GetConfig()
@@ -65,7 +67,8 @@ func TestNewPollingProjectConfigManagerWithNull(t *testing.T) {
6567
options := PollingProjectConfigManagerOptions{
6668
Requester: mockRequester,
6769
}
68-
configManager := NewPollingProjectConfigManagerWithOptions(context.Background(), sdkKey, options)
70+
exeCtx := utils.NewCancelableExecutionCtx()
71+
configManager := NewPollingProjectConfigManagerWithOptions(exeCtx, sdkKey, options)
6972
mockRequester.AssertExpectations(t)
7073

7174
_, err := configManager.GetConfig()

optimizely/event/factory_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package event
22

33
import (
4-
"context"
54
"math/rand"
65
"testing"
76
"time"
87

98
"github.com/optimizely/go-sdk/optimizely"
10-
119
"github.com/optimizely/go-sdk/optimizely/entities"
10+
"github.com/optimizely/go-sdk/optimizely/utils"
1211
"github.com/stretchr/testify/assert"
1312
)
1413

@@ -89,11 +88,10 @@ func BuildTestConversionEvent() UserEvent {
8988
}
9089

9190
func TestCreateAndSendImpressionEvent(t *testing.T) {
92-
ctx := context.Background()
9391

9492
impressionUserEvent := BuildTestImpressionEvent()
9593

96-
processor := NewEventProcessor(ctx, 10, 100, 100)
94+
processor := NewEventProcessor(utils.NewCancelableExecutionCtx(), 10, 100, 100)
9795

9896
processor.ProcessEvent(impressionUserEvent)
9997

@@ -105,11 +103,10 @@ func TestCreateAndSendImpressionEvent(t *testing.T) {
105103
}
106104

107105
func TestCreateAndSendConversionEvent(t *testing.T) {
108-
ctx := context.Background()
109106

110107
conversionUserEvent := BuildTestConversionEvent()
111108

112-
processor := NewEventProcessor(ctx, 10, 100, 100)
109+
processor := NewEventProcessor(utils.NewCancelableExecutionCtx(), 10, 100, 100)
113110

114111
processor.ProcessEvent(conversionUserEvent)
115112

optimizely/event/processor.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package event
2020
import (
2121
"context"
2222
"errors"
23+
"github.com/optimizely/go-sdk/optimizely/utils"
2324
"sync"
2425
"time"
2526

@@ -40,6 +41,8 @@ type QueueingEventProcessor struct {
4041
Mux sync.Mutex
4142
Ticker *time.Ticker
4243
EventDispatcher Dispatcher
44+
45+
wg *sync.WaitGroup
4346
}
4447

4548
// DefaultBatchSize holds the default value for the batch size
@@ -54,19 +57,21 @@ const DefaultEventFlushInterval = 30 * time.Second
5457
var pLogger = logging.GetLogger("EventProcessor")
5558

5659
// NewEventProcessor returns a new instance of QueueingEventProcessor with queueSize and flushInterval
57-
func NewEventProcessor(ctx context.Context, batchSize, queueSize int, flushInterval time.Duration) *QueueingEventProcessor {
60+
func NewEventProcessor(exeCtx utils.ExecutionCtx, batchSize, queueSize int, flushInterval time.Duration) *QueueingEventProcessor {
5861
p := &QueueingEventProcessor{
5962
MaxQueueSize: queueSize,
6063
FlushInterval: flushInterval,
6164
Q: NewInMemoryQueue(queueSize),
6265
EventDispatcher: &HTTPEventDispatcher{},
66+
67+
wg: exeCtx.GetWaitSync(),
6368
}
6469
p.BatchSize = DefaultBatchSize
6570
if batchSize > 0 {
6671
p.BatchSize = batchSize
6772
}
6873

69-
p.StartTicker(ctx)
74+
p.StartTicker(exeCtx.GetContext())
7075
return p
7176
}
7277

@@ -102,7 +107,10 @@ func (p *QueueingEventProcessor) StartTicker(ctx context.Context) {
102107
return
103108
}
104109
p.Ticker = time.NewTicker(p.FlushInterval * time.Millisecond)
110+
p.wg.Add(1)
105111
go func() {
112+
113+
defer p.wg.Done()
106114
for {
107115
select {
108116
case <-p.Ticker.C:

0 commit comments

Comments
 (0)