Skip to content

Commit c54dfb9

Browse files
authored
fix(project-config): add support for multiple clients (#409)
Fix ProjectConfig to support multiple clients (sdkKeys). - full thread-safety for additional concurrency requirements - no resource conflicts for multiple sdkKeys support
1 parent 7b169e8 commit c54dfb9

File tree

4 files changed

+69
-12
lines changed

4 files changed

+69
-12
lines changed

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@
574574
6E623F0F253F9045000617D0 /* DecisionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E623F01253F9045000617D0 /* DecisionInfo.swift */; };
575575
6E636B912236C91F00AF3CEF /* Optimizely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBAEB6C21E3FEF800D13AA9 /* Optimizely.framework */; };
576576
6E636BA02236C96700AF3CEF /* Optimizely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBAEB6C21E3FEF800D13AA9 /* Optimizely.framework */; };
577+
6E6419FE265734C100C49555 /* ProjectConfigTests_MultiClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6419FD265734C100C49555 /* ProjectConfigTests_MultiClients.swift */; };
577578
6E6419DA2657059700C49555 /* NotificationCenterTests_MultiClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */; };
578579
6E6BE00B237F547200FE8274 /* optimizely_config_datafile.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E6BE009237F547200FE8274 /* optimizely_config_datafile.json */; };
579580
6E6BE00C237F547200FE8274 /* optimizely_config_expected.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E6BE00A237F547200FE8274 /* optimizely_config_expected.json */; };
@@ -1851,6 +1852,7 @@
18511852
6E623F01253F9045000617D0 /* DecisionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionInfo.swift; sourceTree = "<group>"; };
18521853
6E636B8C2236C91F00AF3CEF /* OptimizelyTests-APIs-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-APIs-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
18531854
6E636B9B2236C96700AF3CEF /* OptimizelyTests-Legacy-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OptimizelyTests-Legacy-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
1855+
6E6419FD265734C100C49555 /* ProjectConfigTests_MultiClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectConfigTests_MultiClients.swift; sourceTree = "<group>"; };
18541856
6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterTests_MultiClients.swift; sourceTree = "<group>"; };
18551857
6E6BE009237F547200FE8274 /* optimizely_config_datafile.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = optimizely_config_datafile.json; sourceTree = "<group>"; };
18561858
6E6BE00A237F547200FE8274 /* optimizely_config_expected.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = optimizely_config_expected.json; sourceTree = "<group>"; };
@@ -2238,6 +2240,7 @@
22382240
6E474C8C263C889E00ABDFF8 /* UserProfileServiceTests_MultiClients.swift */,
22392241
6EE591192649CF640013AD66 /* LoggerTests_MultiClients.swift */,
22402242
6EE5918D264AF44B0013AD66 /* HandlerRegistryServiceTests_MultiClients.swift */,
2243+
6E6419FD265734C100C49555 /* ProjectConfigTests_MultiClients.swift */,
22412244
6E6419D92657059700C49555 /* NotificationCenterTests_MultiClients.swift */,
22422245
);
22432246
path = "OptimizelyTests-MultiClients";
@@ -3685,6 +3688,7 @@
36853688
buildActionMask = 2147483647;
36863689
files = (
36873690
6E424CEC26324B620081004A /* DefaultLogger.swift in Sources */,
3691+
6E6419FE265734C100C49555 /* ProjectConfigTests_MultiClients.swift in Sources */,
36883692
6E424CED26324B620081004A /* DefaultUserProfileService.swift in Sources */,
36893693
6E424CEE26324B620081004A /* DefaultEventDispatcher.swift in Sources */,
36903694
6E424CEF26324B620081004A /* DefaultDatafileHandler.swift in Sources */,

Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ SPEC CHECKSUMS:
1717

1818
PODFILE CHECKSUM: 8efc37b7d97fa9de16532d755b40b003d888b834
1919

20-
COCOAPODS: 1.9.3
20+
COCOAPODS: 1.10.1

Sources/Data Model/ProjectConfig.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ProjectConfig {
2525
// local runtime forcedVariations [UserId: [ExperimentId: VariationId]]
2626
// NOTE: experiment.forcedVariations use [ExperimentKey: VariationKey] instead of ids
2727

28-
var whitelistUsers = [String: [String: String]]()
28+
var whitelistUsers = AtomicProperty(property: [String: [String: String]]())
2929

3030
lazy var experimentKeyMap: [String: Experiment] = {
3131
var map = [String: Experiment]()
@@ -152,26 +152,30 @@ extension ProjectConfig {
152152
// MARK: - Persistent Data
153153

154154
extension ProjectConfig {
155-
private func whitelistUser(userId: String, experimentId: String, variationId: String) {
156-
var dic = whitelistUsers[userId] ?? [String: String]()
157-
dic[experimentId] = variationId
158-
whitelistUsers[userId] = dic
155+
func whitelistUser(userId: String, experimentId: String, variationId: String) {
156+
whitelistUsers.performAtomic { whitelist in
157+
var dict = whitelist[userId] ?? [String: String]()
158+
dict[experimentId] = variationId
159+
whitelist[userId] = dict
160+
}
159161
}
160162

161-
private func removeFromWhitelist(userId: String, experimentId: String) {
162-
self.whitelistUsers[userId]?.removeValue(forKey: experimentId)
163+
func removeFromWhitelist(userId: String, experimentId: String) {
164+
whitelistUsers.performAtomic { whitelist in
165+
whitelist[userId]?.removeValue(forKey: experimentId)
166+
}
163167
}
164168

165-
private func getWhitelistedVariationId(userId: String, experimentId: String) -> String? {
166-
if let dic = whitelistUsers[userId] {
167-
return dic[experimentId]
169+
func getWhitelistedVariationId(userId: String, experimentId: String) -> String? {
170+
if let dict = whitelistUsers.property?[userId] {
171+
return dict[experimentId]
168172
}
169173

170174
logger.d(.userHasNoForcedVariation(userId))
171175
return nil
172176
}
173177

174-
private func isValidVersion(version: String) -> Bool {
178+
func isValidVersion(version: String) -> Bool {
175179
// old versions (< 4) of datafiles not supported
176180
return ["4"].contains(version)
177181
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// Copyright 2021, Optimizely, Inc. and contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import XCTest
18+
19+
class ProjectConfigTests_MultiClients: XCTestCase {
20+
var config: ProjectConfig!
21+
22+
override func setUpWithError() throws {
23+
config = ProjectConfig()
24+
}
25+
26+
func testConcurrentAccess() {
27+
let numThreads = 10
28+
let numEventsPerThread = 100
29+
30+
let result = OTUtils.runConcurrent(count: numThreads) { thIdx in
31+
for idx in 0..<numEventsPerThread {
32+
let userId = String(idx)
33+
let experimentId = String((thIdx * numEventsPerThread) + idx)
34+
let variationId = experimentId
35+
36+
self.config.whitelistUser(userId: userId, experimentId: experimentId, variationId: variationId)
37+
var result = self.config.getWhitelistedVariationId(userId: userId, experimentId: experimentId)
38+
XCTAssertEqual(result, variationId)
39+
40+
self.config.removeFromWhitelist(userId: userId, experimentId: experimentId)
41+
result = self.config.getWhitelistedVariationId(userId: userId, experimentId: experimentId)
42+
XCTAssertNil(result)
43+
}
44+
}
45+
46+
XCTAssertTrue(result, "Concurrent tasks timed out")
47+
}
48+
49+
}

0 commit comments

Comments
 (0)