diff --git a/OptimizelySwiftSDK.xcodeproj/project.pbxproj b/OptimizelySwiftSDK.xcodeproj/project.pbxproj index a442597c..0231ec92 100644 --- a/OptimizelySwiftSDK.xcodeproj/project.pbxproj +++ b/OptimizelySwiftSDK.xcodeproj/project.pbxproj @@ -2078,6 +2078,22 @@ 98AC984B2DB8FFE0001405DD /* DecisionServiceTests_Holdouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AC98482DB8FC29001405DD /* DecisionServiceTests_Holdouts.swift */; }; 98AC985E2DBA6721001405DD /* OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AC985D2DBA6721001405DD /* OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift */; }; 98AC985F2DBA6721001405DD /* OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AC985D2DBA6721001405DD /* OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift */; }; + 98CBECAC2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECAD2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECAE2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECAF2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB02EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB12EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB22EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB32EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB42EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB52EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB62EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB72EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB82EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECB92EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECBA2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; + 98CBECBB2EB3CA6500C06A62 /* CmabCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */; }; 98D5AE842DBB91C0000D5844 /* OptimizelyUserContextTests_Decide_Holdouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D5AE832DBB91C0000D5844 /* OptimizelyUserContextTests_Decide_Holdouts.swift */; }; 98D5AE852DBB91C0000D5844 /* OptimizelyUserContextTests_Decide_Holdouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D5AE832DBB91C0000D5844 /* OptimizelyUserContextTests_Decide_Holdouts.swift */; }; 98F28A1D2E01940500A86546 /* Cmab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F28A1C2E01940500A86546 /* Cmab.swift */; }; @@ -2587,6 +2603,7 @@ 98AC98452DB7B762001405DD /* BucketTests_HoldoutToVariation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketTests_HoldoutToVariation.swift; sourceTree = ""; }; 98AC98482DB8FC29001405DD /* DecisionServiceTests_Holdouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionServiceTests_Holdouts.swift; sourceTree = ""; }; 98AC985D2DBA6721001405DD /* OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_Decide_With_Holdouts_Reasons.swift; sourceTree = ""; }; + 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmabCache.swift; sourceTree = ""; }; 98D5AE832DBB91C0000D5844 /* OptimizelyUserContextTests_Decide_Holdouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizelyUserContextTests_Decide_Holdouts.swift; sourceTree = ""; }; 98F28A1C2E01940500A86546 /* Cmab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cmab.swift; sourceTree = ""; }; 98F28A2D2E01968000A86546 /* CmabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmabTests.swift; sourceTree = ""; }; @@ -3304,6 +3321,7 @@ children = ( 98F28A402E02DD6D00A86546 /* CmabClient.swift */, 98F28A552E0451CC00A86546 /* CmabService.swift */, + 98CBECAB2EB3CA6500C06A62 /* CmabCache.swift */, ); path = CMAB; sourceTree = ""; @@ -4321,6 +4339,7 @@ 989428BB2DBFA431008BA1C8 /* MockBucketer.swift in Sources */, 6E14CDAB2423F9EB00010234 /* MockUrlSession.swift in Sources */, 6E14CDAA2423F9C300010234 /* SDKVersion.swift in Sources */, + 98CBECB32EB3CA6500C06A62 /* CmabCache.swift in Sources */, 845945C3287758A100D13E11 /* OdpConfig.swift in Sources */, 6E14CD832423F9A100010234 /* DataStoreQueueStackImpl.swift in Sources */, 848617F12863E21200B7F41B /* OdpEventApiManager.swift in Sources */, @@ -4475,6 +4494,7 @@ 6E424D0926324B620081004A /* FeatureVariable.swift in Sources */, 6E6419DA2657059700C49555 /* NotificationCenterTests_MultiClients.swift in Sources */, 6E424D0A26324B620081004A /* Rollout.swift in Sources */, + 98CBECB52EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E2D5DAE26338CA00002077F /* AtomicDictionaryTests.swift in Sources */, 6E424D0B26324B620081004A /* Variation.swift in Sources */, 6E424D0C26324B620081004A /* TrafficAllocation.swift in Sources */, @@ -4606,6 +4626,7 @@ 6EF8DE1B24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, 980CC90D2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75173722C520D400B2B157 /* MurmurHash3.swift in Sources */, + 98CBECB42EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E7517F922C520D400B2B157 /* DataStoreUserDefaults.swift in Sources */, C78CAF592445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, 848617C92863DC2700B7F41B /* OdpSegmentManager.swift in Sources */, @@ -4648,6 +4669,7 @@ 989428B32DBFA431008BA1C8 /* MockBucketer.swift in Sources */, 6E75170222C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */, 6E7516BA22C520D400B2B157 /* DefaultUserProfileService.swift in Sources */, + 98CBECB02EB3CA6500C06A62 /* CmabCache.swift in Sources */, 845945C7287758A300D13E11 /* OdpConfig.swift in Sources */, 6E75175622C520D400B2B157 /* LogMessage.swift in Sources */, 848617F62863E21200B7F41B /* OdpEventApiManager.swift in Sources */, @@ -4866,6 +4888,7 @@ 6E75175E22C520D400B2B157 /* AtomicProperty.swift in Sources */, 6E9B11DE22C548A200C22D81 /* OptimizelyClientTests_Others.swift in Sources */, C78CAF7424482C86009FE876 /* OptimizelyClientTests_OptimizelyJSON.swift in Sources */, + 98CBECBA2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6EC6DD3724ABF6990017D296 /* OptimizelyClient+Decide.swift in Sources */, 6E7516E622C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 98F28A612E0451CC00A86546 /* CmabService.swift in Sources */, @@ -4925,6 +4948,7 @@ 6E75173D22C520D400B2B157 /* MurmurHash3.swift in Sources */, 6E7516E922C520D400B2B157 /* OPTEventDispatcher.swift in Sources */, 6E7518A722C520D400B2B157 /* FeatureFlag.swift in Sources */, + 98CBECB92EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E75187722C520D400B2B157 /* Variation.swift in Sources */, 6E7517F322C520D400B2B157 /* DataStoreMemory.swift in Sources */, 6E7518FB22C520D500B2B157 /* UserAttribute.swift in Sources */, @@ -5095,6 +5119,7 @@ 84E2E94F2852A378001114AB /* VuidManager.swift in Sources */, 6E75176322C520D400B2B157 /* AtomicProperty.swift in Sources */, 6E9B117722C5487100C22D81 /* BatchEventBuilderTests_EventTags.swift in Sources */, + 98CBECB62EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E7517DD22C520D400B2B157 /* DefaultNotificationCenter.swift in Sources */, 6E9B116622C5487100C22D81 /* DecisionServiceTests_UserProfiles.swift in Sources */, 6E34A6202319EBB800BAE302 /* Notifications.swift in Sources */, @@ -5252,6 +5277,7 @@ 6E9B11B822C5489600C22D81 /* OTUtils.swift in Sources */, 98AC97F42DAE9685001405DD /* HoldoutConfigTests.swift in Sources */, 6E9B119022C5488300C22D81 /* AttributeValueTests.swift in Sources */, + 98CBECBB2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E994B4025A3E6EA00999262 /* DecisionResponse.swift in Sources */, 84E2E9802855875E001114AB /* OdpEventManager.swift in Sources */, 6E75175822C520D400B2B157 /* LogMessage.swift in Sources */, @@ -5390,6 +5416,7 @@ 6E4544AF270E67C800F2CEBC /* NetworkReachability.swift in Sources */, 6E75177522C520D400B2B157 /* SDKVersion.swift in Sources */, 848617CD2863DC2700B7F41B /* OdpSegmentManager.swift in Sources */, + 98CBECAE2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E75180722C520D400B2B157 /* DataStoreFile.swift in Sources */, 8486181D286D188B00B7F41B /* OdpEventApiManagerTests.swift in Sources */, 6E75183722C520D400B2B157 /* EventForDispatch.swift in Sources */, @@ -5572,6 +5599,7 @@ 6E424C04263228FD0081004A /* AtomicDictionary.swift in Sources */, 6E75189922C520D400B2B157 /* Experiment.swift in Sources */, 6E75178322C520D400B2B157 /* ArrayEventForDispatch+Extension.swift in Sources */, + 98CBECB12EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E424BE5263228E90081004A /* AtomicArray.swift in Sources */, 6E7518A522C520D400B2B157 /* FeatureFlag.swift in Sources */, ); @@ -5638,6 +5666,7 @@ 6E4544B4270E67C800F2CEBC /* NetworkReachability.swift in Sources */, 6E75184622C520D400B2B157 /* Event.swift in Sources */, 6E7517CE22C520D400B2B157 /* DefaultBucketer.swift in Sources */, + 98CBECB82EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E75180A22C520D400B2B157 /* DataStoreFile.swift in Sources */, 6E7516B822C520D400B2B157 /* DefaultUserProfileService.swift in Sources */, 6E7517C222C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, @@ -5747,6 +5776,7 @@ 6E4544B9270E67C800F2CEBC /* NetworkReachability.swift in Sources */, 6E75184B22C520D400B2B157 /* Event.swift in Sources */, 6E7517D322C520D400B2B157 /* DefaultBucketer.swift in Sources */, + 98CBECAD2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E75180F22C520D400B2B157 /* DataStoreFile.swift in Sources */, 6E7516BD22C520D400B2B157 /* DefaultUserProfileService.swift in Sources */, 6E7517C722C520D400B2B157 /* DefaultDatafileHandler.swift in Sources */, @@ -5861,6 +5891,7 @@ 6EF8DE1A24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, 980CC90B2D833F2800E07D24 /* ExperimentCore.swift in Sources */, 6E75186422C520D400B2B157 /* Rollout.swift in Sources */, + 98CBECAF2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 6E75179622C520D400B2B157 /* DataStoreQueueStackImpl+Extension.swift in Sources */, C78CAF582445AD8D009FE876 /* OptimizelyJSON.swift in Sources */, 848617C82863DC2700B7F41B /* OdpSegmentManager.swift in Sources */, @@ -5903,6 +5934,7 @@ 989428BE2DBFA431008BA1C8 /* MockBucketer.swift in Sources */, 6E7516FC22C520D400B2B157 /* OptimizelyLogLevel.swift in Sources */, 6E7516B422C520D400B2B157 /* DefaultUserProfileService.swift in Sources */, + 98CBECAC2EB3CA6500C06A62 /* CmabCache.swift in Sources */, 845945C02877589F00D13E11 /* OdpConfig.swift in Sources */, 6E75175022C520D400B2B157 /* LogMessage.swift in Sources */, 848617EE2863E21200B7F41B /* OdpEventApiManager.swift in Sources */, @@ -6089,6 +6121,7 @@ 75C71A3B25E454460084187E /* ArrayEventForDispatch+Extension.swift in Sources */, 75C71A3C25E454460084187E /* OptimizelyClient+Extension.swift in Sources */, 75C71A3D25E454460084187E /* DataStoreQueueStackImpl+Extension.swift in Sources */, + 98CBECB72EB3CA6500C06A62 /* CmabCache.swift in Sources */, 845945BF2877589F00D13E11 /* OdpConfig.swift in Sources */, 75C71A3E25E454460084187E /* Array+Extension.swift in Sources */, 75C71A3F25E454460084187E /* Constants.swift in Sources */, @@ -6172,6 +6205,7 @@ 6EF8DE1C24BD1BB2008B9488 /* OptimizelyDecideOption.swift in Sources */, 980CC9132D833F2800E07D24 /* ExperimentCore.swift in Sources */, BD6485642491474500F30986 /* Rollout.swift in Sources */, + 98CBECB22EB3CA6500C06A62 /* CmabCache.swift in Sources */, BD6485652491474500F30986 /* DataStoreQueueStackImpl+Extension.swift in Sources */, BD6485662491474500F30986 /* OptimizelyJSON.swift in Sources */, 848617CA2863DC2700B7F41B /* OdpSegmentManager.swift in Sources */, diff --git a/README.md b/README.md index d068f006..983acfbe 100644 --- a/README.md +++ b/README.md @@ -129,4 +129,4 @@ Used to enforce Swift style and conventions. - React - https://github.com/optimizely/react-sdk -- Ruby - https://github.com/optimizely/ruby-sdk \ No newline at end of file +- Ruby - https://github.com/optimizely/ruby-sdk \ No newline at end of file diff --git a/Sources/CMAB/CmabCache.swift b/Sources/CMAB/CmabCache.swift new file mode 100644 index 00000000..cbf53a16 --- /dev/null +++ b/Sources/CMAB/CmabCache.swift @@ -0,0 +1,29 @@ +// +// Copyright 2022, Optimizely, Inc. and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +let DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 // 30 minutes +let DEFAULT_CMAB_CACHE_SIZE = 100 + +class CmabCache: LruCache { + override func lookup(key: String) -> CmabCacheValue? { + if timeoutInSecs <= 0 { + return nil + } + return super.lookup(key: key) + } +} diff --git a/Sources/CMAB/CmabClient.swift b/Sources/CMAB/CmabClient.swift index 1e69c3d9..e94f941c 100644 --- a/Sources/CMAB/CmabClient.swift +++ b/Sources/CMAB/CmabClient.swift @@ -32,7 +32,7 @@ enum CmabClientError: Error, Equatable { } struct CmabRetryConfig { - var maxRetries: Int = 3 + var maxRetries: Int = 1 var initialBackoff: TimeInterval = 0.1 // seconds var maxBackoff: TimeInterval = 10.0 // seconds var backoffMultiplier: Double = 2.0 diff --git a/Sources/CMAB/CmabService.swift b/Sources/CMAB/CmabService.swift index 20c33f82..5dd44b04 100644 --- a/Sources/CMAB/CmabService.swift +++ b/Sources/CMAB/CmabService.swift @@ -45,13 +45,13 @@ class DefaultCmabService: CmabService { typealias UserAttributes = [String : Any?] private let cmabClient: CmabClient - private let cmabCache: LruCache + let cmabCache: CmabCache private let logger = OPTLoggerFactory.getLogger() private static let NUM_LOCKS = 1000 private let locks: [NSLock] - init(cmabClient: CmabClient, cmabCache: LruCache) { + init(cmabClient: CmabClient, cmabCache: CmabCache) { self.cmabClient = cmabClient self.cmabCache = cmabCache self.locks = (0.. DefaultCmabService { - let DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000 // 30 minutes in milliseconds - let DEFAULT_CMAB_CACHE_SIZE = 1000 - let cache = LruCache(size: DEFAULT_CMAB_CACHE_SIZE, timeoutInSecs: DEFAULT_CMAB_CACHE_TIMEOUT) + let cache = CmabCache(size: DEFAULT_CMAB_CACHE_SIZE, timeoutInSecs: DEFAULT_CMAB_CACHE_TIMEOUT) + return DefaultCmabService(cmabClient: DefaultCmabClient(), cmabCache: cache) + } + + static func createDefault(cacheSize: Int, cacheTimeout: Int) -> DefaultCmabService { + let cache = CmabCache(size: cacheSize, timeoutInSecs: cacheTimeout) return DefaultCmabService(cmabClient: DefaultCmabClient(), cmabCache: cache) } } diff --git a/Sources/ODP/OptimizelySdkSettings.swift b/Sources/ODP/OptimizelySdkSettings.swift index fc3d4713..5887e372 100644 --- a/Sources/ODP/OptimizelySdkSettings.swift +++ b/Sources/ODP/OptimizelySdkSettings.swift @@ -25,6 +25,10 @@ public struct OptimizelySdkSettings { let timeoutForSegmentFetchInSecs: Int /// The timeout in seconds of odp event dispatch - OS default timeout will be used if this is set to zero. let timeoutForOdpEventInSecs: Int + /// The maximum size of cmab cache + let cmabCacheSize: Int + /// The timeout in seconds of cmab cache + let cmabCacheTimeoutInSecs: Int /// ODP features are disabled if this is set to true. let disableOdp: Bool /// VUID is enabled if this is set to true. @@ -37,6 +41,8 @@ public struct OptimizelySdkSettings { /// - segmentsCacheTimeoutInSecs: The timeout in seconds of audience segments cache (optional. default = 600). Set to zero to disable timeout. /// - timeoutForSegmentFetchInSecs: The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero. /// - timeoutForOdpEventInSecs: The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero. + /// - cmabCacheSize: The maximum size of cmab cache (optional. default = 100). + /// - cmabCacheTimeoutInSecs: The timeout in seconds of amb cache (optional. default = 30 * 60). /// - disableOdp: Set this flag to true (default = false) to disable ODP features /// - enableVuid: Set this flag to true (default = false) to enable vuid. /// - sdkName: Set this flag to override sdkName included in events @@ -45,12 +51,16 @@ public struct OptimizelySdkSettings { segmentsCacheTimeoutInSecs: Int = 600, timeoutForSegmentFetchInSecs: Int = 10, timeoutForOdpEventInSecs: Int = 10, + cmabCacheSize: Int = 100, + cmabCacheTimeoutInSecs: Int = 30 * 60, disableOdp: Bool = false, enableVuid: Bool = false, sdkName: String? = nil, sdkVersion: String? = nil) { self.segmentsCacheSize = segmentsCacheSize self.segmentsCacheTimeoutInSecs = segmentsCacheTimeoutInSecs + self.cmabCacheSize = cmabCacheSize + self.cmabCacheTimeoutInSecs = cmabCacheTimeoutInSecs self.timeoutForSegmentFetchInSecs = timeoutForSegmentFetchInSecs self.timeoutForOdpEventInSecs = timeoutForOdpEventInSecs self.disableOdp = disableOdp diff --git a/Sources/Optimizely/OptimizelyClient.swift b/Sources/Optimizely/OptimizelyClient.swift index 8dcf525f..f3d4993f 100644 --- a/Sources/Optimizely/OptimizelyClient.swift +++ b/Sources/Optimizely/OptimizelyClient.swift @@ -108,7 +108,7 @@ open class OptimizelyClient: NSObject { let logger = logger ?? DefaultLogger() type(of: logger).logLevel = defaultLogLevel ?? .info - let cmabService = DefaultCmabService.createDefault() + let cmabService = DefaultCmabService.createDefault(cacheSize: self.sdkSettings.cmabCacheSize, cacheTimeout: self.sdkSettings.cmabCacheTimeoutInSecs) self.registerServices(sdkKey: sdkKey, logger: logger, diff --git a/Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift b/Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift index 882aa717..516dca5d 100644 --- a/Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift +++ b/Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift @@ -37,7 +37,9 @@ class OptimizelyClientTests_ODP: XCTestCase { func testSdkSettings_default() { let optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey) - + let cmabCache = ((optimizely.decisionService as! DefaultDecisionService).cmabService as! DefaultCmabService).cmabCache + XCTAssertEqual(100, cmabCache.maxSize) + XCTAssertEqual(30 * 60, cmabCache.timeoutInSecs) XCTAssertEqual(100, optimizely.odpManager.segmentManager?.segmentsCache.maxSize) XCTAssertEqual(600, optimizely.odpManager.segmentManager?.segmentsCache.timeoutInSecs) XCTAssertEqual(10, optimizely.odpManager.segmentManager?.apiMgr.resourceTimeoutInSecs) @@ -47,8 +49,13 @@ class OptimizelyClientTests_ODP: XCTestCase { func testSdkSettings_custom() { var sdkSettings = OptimizelySdkSettings(segmentsCacheSize: 12, - segmentsCacheTimeoutInSecs: 345) + segmentsCacheTimeoutInSecs: 345, + cmabCacheSize: 50, + cmabCacheTimeoutInSecs: 120) var optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: sdkSettings) + let cmabCache = ((optimizely.decisionService as! DefaultDecisionService).cmabService as! DefaultCmabService).cmabCache + XCTAssertEqual(50, cmabCache.maxSize) + XCTAssertEqual(120, cmabCache.timeoutInSecs) XCTAssertEqual(12, optimizely.odpManager.segmentManager?.segmentsCache.maxSize) XCTAssertEqual(345, optimizely.odpManager.segmentManager?.segmentsCache.timeoutInSecs) diff --git a/Tests/OptimizelyTests-Common/CmabServiceTests.swift b/Tests/OptimizelyTests-Common/CmabServiceTests.swift index f79d2f16..79b8dba0 100644 --- a/Tests/OptimizelyTests-Common/CmabServiceTests.swift +++ b/Tests/OptimizelyTests-Common/CmabServiceTests.swift @@ -111,7 +111,7 @@ class MockUserContext: OptimizelyUserContext { class DefaultCmabServiceTests: XCTestCase { fileprivate var cmabClient: MockCmabClient! fileprivate var config: MockProjectConfig! - var cmabCache: LruCache! + var cmabCache: CmabCache! var cmabService: DefaultCmabService! var userContext: OptimizelyUserContext! let userAttributes: [String: Any] = ["age": 25, "location": "San Francisco"] @@ -120,7 +120,7 @@ class DefaultCmabServiceTests: XCTestCase { super.setUp() config = MockProjectConfig() cmabClient = MockCmabClient() - cmabCache = LruCache(size: 10, timeoutInSecs: 10) + cmabCache = CmabCache(size: 10, timeoutInSecs: 10) cmabService = DefaultCmabService(cmabClient: cmabClient, cmabCache: cmabCache) // Set up user context userContext = MockUserContext(userId: "test-user", attributes: userAttributes) @@ -657,3 +657,220 @@ extension DefaultCmabServiceTests { } } + +extension DefaultCmabServiceTests { + + func testCacheSizeZero() { + // Create a cache with size 0 (no caching) + let zeroCmabCache = CmabCache(size: 0, timeoutInSecs: 10) + let zeroCacheService = DefaultCmabService(cmabClient: cmabClient, cmabCache: zeroCmabCache) + + cmabClient.fetchDecisionResult = .success("variation-first") + + let expectation1 = self.expectation(description: "first request") + + // First request + zeroCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + switch result { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-first") + XCTAssertTrue(self.cmabClient.fetchDecisionCalled, "Should call API on first request") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + expectation1.fulfill() + } + + wait(for: [expectation1], timeout: 1.0) + + // Reset and change the variation + cmabClient.reset() + cmabClient.fetchDecisionResult = .success("variation-second") + + let expectation2 = self.expectation(description: "second request") + + // Second request - should NOT use cache (size = 0) + zeroCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + switch result { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-second") + XCTAssertTrue(self.cmabClient.fetchDecisionCalled, "Should call API again when cache size is 0") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + expectation2.fulfill() + } + + wait(for: [expectation2], timeout: 1.0) + } + + func testCacheTimeoutZero() { + // Create a cache with timeout 0 (immediate expiration) + let zeroTimeoutCache = CmabCache(size: 10, timeoutInSecs: 0) + let zeroTimeoutService = DefaultCmabService(cmabClient: cmabClient, cmabCache: zeroTimeoutCache) + + cmabClient.fetchDecisionResult = .success("variation-first") + + let expectation1 = self.expectation(description: "first request") + + // First request + zeroTimeoutService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + switch result { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-first") + XCTAssertTrue(self.cmabClient.fetchDecisionCalled, "Should call API on first request") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + expectation1.fulfill() + } + + wait(for: [expectation1], timeout: 1.0) + + // Reset and change the variation + cmabClient.reset() + cmabClient.fetchDecisionResult = .success("variation-second") + + // Small delay to ensure cache timeout + Thread.sleep(forTimeInterval: 1.1) + + let expectation2 = self.expectation(description: "second request") + + // Second request - should NOT use cache (timeout = 0, cache expired) + zeroTimeoutService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + switch result { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-second") + XCTAssertTrue(self.cmabClient.fetchDecisionCalled, "Should call API again when cache timeout is 0") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + expectation2.fulfill() + } + + wait(for: [expectation2], timeout: 1.0) + } + + func testCacheSizeZeroAndTimeoutZero() { + // Create a cache with both size 0 and timeout 0 (completely disabled) + let disabledCache = CmabCache(size: 0, timeoutInSecs: 0) + let disabledCacheService = DefaultCmabService(cmabClient: cmabClient, cmabCache: disabledCache) + + cmabClient.fetchDecisionResult = .success("variation-1") + + let expectation1 = self.expectation(description: "first request") + + // First request + disabledCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + switch result { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-1") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + expectation1.fulfill() + } + + wait(for: [expectation1], timeout: 1.0) + + let apiCallCount1 = cmabClient.fetchDecisionCalled ? 1 : 0 + + // Reset and make multiple requests + cmabClient.reset() + cmabClient.fetchDecisionResult = .success("variation-2") + + let expectation2 = self.expectation(description: "second request") + + disabledCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) { result in + expectation2.fulfill() + } + + wait(for: [expectation2], timeout: 1.0) + + let apiCallCount2 = cmabClient.fetchDecisionCalled ? 1 : 0 + + XCTAssertEqual(apiCallCount1, 1, "First request should call API") + XCTAssertEqual(apiCallCount2, 1, "Second request should also call API (no caching)") + } + + func testSyncCacheSizeZero() { + // Create a cache with size 0 + let zeroCmabCache = CmabCache(size: 0, timeoutInSecs: 10) + let zeroCacheService = DefaultCmabService(cmabClient: cmabClient, cmabCache: zeroCmabCache) + + cmabClient.fetchDecisionResult = .success("variation-first") + + // First request + let result1 = zeroCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) + + switch result1 { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-first") + XCTAssertTrue(cmabClient.fetchDecisionCalled, "Should call API on first request") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + + // Reset and change variation + cmabClient.reset() + cmabClient.fetchDecisionResult = .success("variation-second") + + // Second request - should NOT use cache + let result2 = zeroCacheService.getDecision( + config: config, + userContext: userContext, + ruleId: "exp-123", + options: [] + ) + + switch result2 { + case .success(let decision): + XCTAssertEqual(decision.variationId, "variation-second") + XCTAssertTrue(cmabClient.fetchDecisionCalled, "Should call API again when cache size is 0") + + case .failure(let error): + XCTFail("Expected success but got error: \(error)") + } + } +} diff --git a/Tests/OptimizelyTests-Common/DecisionListenerTests.swift b/Tests/OptimizelyTests-Common/DecisionListenerTests.swift index 8e885b2a..30c85d42 100644 --- a/Tests/OptimizelyTests-Common/DecisionListenerTests.swift +++ b/Tests/OptimizelyTests-Common/DecisionListenerTests.swift @@ -1246,13 +1246,12 @@ class FakeManager: OptimizelyClient { } class FakeDecisionService: DefaultDecisionService { - var experiment: Experiment? var variation: Variation? var source: String! override init(userProfileService: OPTUserProfileService, cmabService: CmabService = DefaultCmabService.createDefault()) { - super.init(userProfileService: DefaultUserProfileService()) + super.init(userProfileService: DefaultUserProfileService(), cmabService: cmabService) } override func getVariationForFeature(config: ProjectConfig, diff --git a/Tests/OptimizelyTests-Common/DecisionServiceTests_Experiments.swift b/Tests/OptimizelyTests-Common/DecisionServiceTests_Experiments.swift index 80bd62cc..8217002a 100644 --- a/Tests/OptimizelyTests-Common/DecisionServiceTests_Experiments.swift +++ b/Tests/OptimizelyTests-Common/DecisionServiceTests_Experiments.swift @@ -887,7 +887,7 @@ fileprivate class MockCmabService: DefaultCmabService { var variationId: String? init() { - super.init(cmabClient: DefaultCmabClient(), cmabCache: LruCache(size: 10, timeoutInSecs: 10)) + super.init(cmabClient: DefaultCmabClient(), cmabCache: CmabCache(size: 10, timeoutInSecs: 10)) } override func getDecision(config: ProjectConfig, userContext: OptimizelyUserContext, ruleId: String, options: [OptimizelyDecideOption]) -> Result { diff --git a/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_Async.swift b/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_Async.swift index 70dc3d6b..8d678722 100644 --- a/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_Async.swift +++ b/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_Async.swift @@ -485,7 +485,7 @@ fileprivate class MockCmabService: DefaultCmabService { var ignoreCacheUsed = false init() { - super.init(cmabClient: DefaultCmabClient(), cmabCache: LruCache(size: 10, timeoutInSecs: 10)) + super.init(cmabClient: DefaultCmabClient(), cmabCache: CmabCache(size: 10, timeoutInSecs: 10)) } override func getDecision(config: ProjectConfig, userContext: OptimizelyUserContext, ruleId: String, options: [OptimizelyDecideOption]) -> Result { diff --git a/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_CMAB.swift b/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_CMAB.swift index e9046953..84fa2401 100644 --- a/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_CMAB.swift +++ b/Tests/OptimizelyTests-Common/OptimizelyUserContextTests_Decide_CMAB.swift @@ -284,7 +284,7 @@ fileprivate class MockCmabService: DefaultCmabService { var invalidateUserCmabCache = false init() { - super.init(cmabClient: DefaultCmabClient(), cmabCache: LruCache(size: 10, timeoutInSecs: 10)) + super.init(cmabClient: DefaultCmabClient(), cmabCache: CmabCache(size: 10, timeoutInSecs: 10)) } override func getDecision(config: ProjectConfig, userContext: OptimizelyUserContext, ruleId: String, options: [OptimizelyDecideOption]) -> Result {