Skip to content

Commit a3a8c14

Browse files
authored
fix(ups): add support for multiple clients (#405)
Fix user-profile-service to support multiple clients (sdkKeys). - full thread-safety for additional concurrency requirements - no resource conflicts for multiple sdkKeys support
1 parent b56f9ba commit a3a8c14

File tree

9 files changed

+235
-119
lines changed

9 files changed

+235
-119
lines changed

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@
540540
6E424D5326324C4D0081004A /* OptimizelyUserContext+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E86CEA124FDC836005DAFED /* OptimizelyUserContext+ObjC.swift */; };
541541
6E424D5426324C4D0081004A /* OptimizelyUserContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC6DD4024ABF89B0017D296 /* OptimizelyUserContext.swift */; };
542542
6E424D7626324DBD0081004A /* AtomicArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E424D7526324DBD0081004A /* AtomicArrayTests.swift */; };
543+
6E474C8D263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */; };
543544
6E593FB625BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E593FB425BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift */; };
544545
6E5AB69323F6130D007A82B1 /* OptimizelyClientTests_Init_Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5AB69123F6130C007A82B1 /* OptimizelyClientTests_Init_Sync.swift */; };
545546
6E5AB69423F6130D007A82B1 /* OptimizelyClientTests_Init_Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5AB69223F6130D007A82B1 /* OptimizelyClientTests_Init_Async.swift */; };
@@ -1329,7 +1330,6 @@
13291330
6E9B115822C5486E00C22D81 /* EventDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199222C5211100B2B157 /* EventDispatcherTests.swift */; };
13301331
6E9B115922C5486E00C22D81 /* BatchEventBuilderTests_Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199322C5211100B2B157 /* BatchEventBuilderTests_Attributes.swift */; };
13311332
6E9B115A22C5486E00C22D81 /* DecisionServiceTests_Others.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199422C5211100B2B157 /* DecisionServiceTests_Others.swift */; };
1332-
6E9B115B22C5486E00C22D81 /* DecisionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199522C5211100B2B157 /* DecisionServiceTests.swift */; };
13331333
6E9B115C22C5486E00C22D81 /* DatafileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199622C5211100B2B157 /* DatafileHandlerTests.swift */; };
13341334
6E9B115D22C5486E00C22D81 /* BatchEventBuilderTests_EventTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199722C5211100B2B157 /* BatchEventBuilderTests_EventTags.swift */; };
13351335
6E9B115E22C5486E00C22D81 /* DataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199822C5211100B2B157 /* DataStoreTests.swift */; };
@@ -1354,7 +1354,6 @@
13541354
6E9B117222C5487100C22D81 /* EventDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199222C5211100B2B157 /* EventDispatcherTests.swift */; };
13551355
6E9B117322C5487100C22D81 /* BatchEventBuilderTests_Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199322C5211100B2B157 /* BatchEventBuilderTests_Attributes.swift */; };
13561356
6E9B117422C5487100C22D81 /* DecisionServiceTests_Others.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199422C5211100B2B157 /* DecisionServiceTests_Others.swift */; };
1357-
6E9B117522C5487100C22D81 /* DecisionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199522C5211100B2B157 /* DecisionServiceTests.swift */; };
13581357
6E9B117622C5487100C22D81 /* DatafileHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199622C5211100B2B157 /* DatafileHandlerTests.swift */; };
13591358
6E9B117722C5487100C22D81 /* BatchEventBuilderTests_EventTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199722C5211100B2B157 /* BatchEventBuilderTests_EventTags.swift */; };
13601359
6E9B117822C5487100C22D81 /* DataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75199822C5211100B2B157 /* DataStoreTests.swift */; };
@@ -1838,6 +1837,7 @@
18381837
6E424BFB263228FD0081004A /* AtomicDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicDictionary.swift; sourceTree = "<group>"; };
18391838
6E424C3C263249620081004A /* OptimizelyTests-MultiClients-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-MultiClients-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
18401839
6E424D7526324DBD0081004A /* AtomicArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicArrayTests.swift; sourceTree = "<group>"; };
1840+
6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileServiceTests_MultiClients.swift; sourceTree = "<group>"; };
18411841
6E593FB425BB9C5500EC72BC /* OptimizelyClientTests_Decide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Decide.swift; sourceTree = "<group>"; };
18421842
6E5AB69123F6130C007A82B1 /* OptimizelyClientTests_Init_Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Sync.swift; sourceTree = "<group>"; };
18431843
6E5AB69223F6130D007A82B1 /* OptimizelyClientTests_Init_Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptimizelyClientTests_Init_Async.swift; sourceTree = "<group>"; };
@@ -1958,7 +1958,6 @@
19581958
6E75199222C5211100B2B157 /* EventDispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDispatcherTests.swift; sourceTree = "<group>"; };
19591959
6E75199322C5211100B2B157 /* BatchEventBuilderTests_Attributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchEventBuilderTests_Attributes.swift; sourceTree = "<group>"; };
19601960
6E75199422C5211100B2B157 /* DecisionServiceTests_Others.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionServiceTests_Others.swift; sourceTree = "<group>"; };
1961-
6E75199522C5211100B2B157 /* DecisionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionServiceTests.swift; sourceTree = "<group>"; };
19621961
6E75199622C5211100B2B157 /* DatafileHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatafileHandlerTests.swift; sourceTree = "<group>"; };
19631962
6E75199722C5211100B2B157 /* BatchEventBuilderTests_EventTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchEventBuilderTests_EventTags.swift; sourceTree = "<group>"; };
19641963
6E75199822C5211100B2B157 /* DataStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreTests.swift; sourceTree = "<group>"; };
@@ -2230,6 +2229,7 @@
22302229
6E424D7526324DBD0081004A /* AtomicArrayTests.swift */,
22312230
6E2D5DAD26338CA00002077F /* AtomicDictionaryTests.swift */,
22322231
6E5D120C2638DCE1000ABFC3 /* EventDispatcherTests_MultiClients.swift */,
2232+
6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */,
22332233
);
22342234
path = "OptimizelyTests-MultiClients";
22352235
sourceTree = "<group>";
@@ -2517,7 +2517,6 @@
25172517
6E75199222C5211100B2B157 /* EventDispatcherTests.swift */,
25182518
6E75199322C5211100B2B157 /* BatchEventBuilderTests_Attributes.swift */,
25192519
6E75199422C5211100B2B157 /* DecisionServiceTests_Others.swift */,
2520-
6E75199522C5211100B2B157 /* DecisionServiceTests.swift */,
25212520
6E75199622C5211100B2B157 /* DatafileHandlerTests.swift */,
25222521
6E75199722C5211100B2B157 /* BatchEventBuilderTests_EventTags.swift */,
25232522
6E75199822C5211100B2B157 /* DataStoreTests.swift */,
@@ -3707,6 +3706,7 @@
37073706
6E424D0526324B620081004A /* ConditionHolder.swift in Sources */,
37083707
6E424D5226324C4D0081004A /* OptimizelyClient+Decide.swift in Sources */,
37093708
6E424D0626324B620081004A /* UserAttribute.swift in Sources */,
3709+
6E474C8D263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift in Sources */,
37103710
6E8A3D4A2637408500DAEA13 /* MockDatafileHandler.swift in Sources */,
37113711
6E424D0726324B620081004A /* Event.swift in Sources */,
37123712
6E424D0826324B620081004A /* ProjectConfig.swift in Sources */,
@@ -4125,7 +4125,6 @@
41254125
6E9B116E22C5487100C22D81 /* LoggerTests.swift in Sources */,
41264126
6E75180D22C520D400B2B157 /* DataStoreFile.swift in Sources */,
41274127
6E75178722C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */,
4128-
6E9B117522C5487100C22D81 /* DecisionServiceTests.swift in Sources */,
41294128
6E75179F22C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */,
41304129
6E7516BB22C520D400B2B157 /* DefaultUserProfileService.swift in Sources */,
41314130
6E424C08263228FD0081004A /* AtomicDictionary.swift in Sources */,
@@ -4348,7 +4347,6 @@
43484347
6E9B115422C5486E00C22D81 /* LoggerTests.swift in Sources */,
43494348
6E7518DF22C520D400B2B157 /* ConditionLeaf.swift in Sources */,
43504349
6E75172D22C520D400B2B157 /* Constants.swift in Sources */,
4351-
6E9B115B22C5486E00C22D81 /* DecisionServiceTests.swift in Sources */,
43524350
6E75172122C520D400B2B157 /* OptimizelyResult.swift in Sources */,
43534351
6E75186722C520D400B2B157 /* Rollout.swift in Sources */,
43544352
6E424C01263228FD0081004A /* AtomicDictionary.swift in Sources */,

Sources/Extensions/OptimizelyClient+Extension.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,17 @@ extension OptimizelyClient {
2323
datafileHandler: OPTDatafileHandler,
2424
decisionService: OPTDecisionService,
2525
notificationCenter: OPTNotificationCenter) {
26-
// bind it as a non-singleton. so, we will create an instance anytime injected.
27-
// we don't associate the logger with a sdkKey at this time because not all components are sdkKey specific.
28-
let binder: Binder = Binder<OPTLogger>(service: OPTLogger.self, factory: type(of: logger).init)
26+
// Register my logger service. Bind it as a non-singleton. So, we will create an instance anytime injected.
27+
// we don't associate the logger with a sdkKey at this time because not all components are sdkKey specific.
28+
HandlerRegistryService.shared.registerBinding(binder: Binder<OPTLogger>(service: OPTLogger.self, factory: type(of: logger).init))
2929

30-
// Register my logger service.
31-
HandlerRegistryService.shared.registerBinding(binder: binder)
32-
33-
// this is bound a reusable singleton. so, if we re-initalize, we will keep this.
30+
// This is bound a reusable singleton. so, if we re-initalize, we will keep this.
3431
HandlerRegistryService.shared.registerBinding(binder: Binder<OPTNotificationCenter>(sdkKey: sdkKey, service: OPTNotificationCenter.self, strategy: .reUse, isSingleton: true, inst: notificationCenter))
35-
// the decision service is also a singleton that will reCreate on re-initalize
32+
33+
// The decision service is also a singleton that will reCreate on re-initalize
3634
HandlerRegistryService.shared.registerBinding(binder: Binder<OPTDecisionService>(sdkKey: sdkKey, service: OPTDecisionService.self, strategy: .reUse, isSingleton: true, inst: decisionService))
3735

38-
// An event dispatcher. We use a singleton and use the same Event dispatcher for all
36+
// An event dispatcher. We use a singleton and use the same Event dispatcher for all
3937
// projects. If you change the event dispatcher, you can potentially lose data if you
4038
// don't use the same backingstore.
4139
HandlerRegistryService.shared.registerBinding(binder: Binder<OPTEventDispatcher>(sdkKey: sdkKey, service: OPTEventDispatcher.self, strategy: .reUse, isSingleton: true, inst: eventDispatcher))

Sources/Implementation/DefaultDecisionService.swift

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class DefaultDecisionService: OPTDecisionService {
2828
let userProfileService: OPTUserProfileService
2929
lazy var logger = OPTLoggerFactory.getLogger()
3030

31+
// user-profile-service read-modify-write lock for supporting multiple clients
32+
static let upsRMWLock = DispatchQueue(label: "ups-rmw")
33+
3134
init(userProfileService: OPTUserProfileService) {
3235
self.bucketer = DefaultBucketer()
3336
self.userProfileService = userProfileService
@@ -388,17 +391,19 @@ extension DefaultDecisionService {
388391
func saveProfile(userId: String,
389392
experimentId: String,
390393
variationId: String) {
391-
var profile = userProfileService.lookup(userId: userId) ?? OPTUserProfileService.UPProfile()
392-
393-
var bucketMap = profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap ?? OPTUserProfileService.UPBucketMap()
394-
bucketMap[experimentId] = [UserProfileKeys.kVariationId: variationId]
395-
396-
profile[UserProfileKeys.kBucketMap] = bucketMap
397-
profile[UserProfileKeys.kUserId] = userId
398-
399-
userProfileService.save(userProfile: profile)
400-
401-
logger.i(.savedVariationInUserProfile(variationId, experimentId, userId))
394+
DefaultDecisionService.upsRMWLock.sync {
395+
var profile = self.userProfileService.lookup(userId: userId) ?? OPTUserProfileService.UPProfile()
396+
397+
var bucketMap = profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap ?? OPTUserProfileService.UPBucketMap()
398+
bucketMap[experimentId] = [UserProfileKeys.kVariationId: variationId]
399+
400+
profile[UserProfileKeys.kBucketMap] = bucketMap
401+
profile[UserProfileKeys.kUserId] = userId
402+
403+
self.userProfileService.save(userProfile: profile)
404+
405+
self.logger.i(.savedVariationInUserProfile(variationId, experimentId, userId))
406+
}
402407
}
403408

404409
}

Sources/Optimizely/OptimizelyClient.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ open class OptimizelyClient: NSObject {
5151
return HandlerRegistryService.shared.injectEventDispatcher(sdkKey: self.sdkKey)
5252
}
5353

54+
public var datafileHandler: OPTDatafileHandler? {
55+
return HandlerRegistryService.shared.injectDatafileHandler(sdkKey: self.sdkKey)
56+
}
57+
lazy var currentDatafileHandler = datafileHandler
58+
5459
// MARK: - Default Services
5560

5661
var decisionService: OPTDecisionService {
5762
return HandlerRegistryService.shared.injectDecisionService(sdkKey: self.sdkKey)!
5863
}
5964

60-
public var datafileHandler: OPTDatafileHandler? {
61-
return HandlerRegistryService.shared.injectDatafileHandler(sdkKey: self.sdkKey)
62-
}
63-
lazy var currentDatafileHandler = datafileHandler
64-
6565
public var notificationCenter: OPTNotificationCenter? {
6666
return HandlerRegistryService.shared.injectNotificationCenter(sdkKey: self.sdkKey)
6767
}

Sources/Utils/HandlerRegistryService.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,12 @@ class HandlerRegistryService {
2626
}
2727

2828
var binders: AtomicProperty<[ServiceKey: BinderProtocol]> = {
29-
3029
var binders = AtomicProperty<[ServiceKey: BinderProtocol]>()
3130
binders.property = [ServiceKey: BinderProtocol]()
3231
return binders
3332
}()
3433

35-
private init() {
36-
37-
}
34+
private init() {}
3835

3936
func registerBinding(binder: BinderProtocol) {
4037
let sk = ServiceKey(service: "\(type(of: binder.service))", sdkKey: binder.sdkKey)
@@ -105,17 +102,15 @@ protocol BinderProtocol {
105102
var strategy: ReInitializeStrategy { get }
106103
var service: Any { get }
107104
var isSingleton: Bool { get }
108-
var factory:()->Any? { get }
109-
// var configure:(_ inst:Any?)->Any? { get }
105+
var factory: () -> Any? { get }
110106
var instance: Any? { get set }
111107

112108
}
113109
struct Binder<T>: BinderProtocol {
114110
var sdkKey: String?
115111
var service: Any
116112
var strategy: ReInitializeStrategy = .reCreate
117-
var factory: (() -> Any?) = { ()->Any? in { return nil as Any? }}
118-
// var configure: ((Any?) -> Any?) = { (_)->Any? in { return nil as Any? }}
113+
var factory: () -> Any? = { return nil as Any? }
119114
var isSingleton = false
120115
var inst: T?
121116

0 commit comments

Comments
 (0)