From 14b3fbc264781227028de62f9d2229f9de6f9508 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 21 Aug 2025 12:06:36 -0300 Subject: [PATCH 01/17] Passing SegmentsInUse between storages bypassing CoreData --- .../Refresh/SplitsSyncHelper.swift | 4 ++- Split/Localhost/LocalhostSplitsStorage.swift | 7 ++-- .../Network/Streaming/SyncUpdateWorker.swift | 34 +++++++++---------- .../RuleBasedSegmentsStorage.swift | 5 ++- Split/Storage/Splits/SplitsStorage.swift | 12 ++++++- .../Fake/Storage/SplitsStorageStub.swift | 4 +++ 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index 6b5a3e8d2..e65a86e36 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -27,7 +27,7 @@ class SplitsSyncHelper { private let splitFetcher: HttpSplitFetcher private let splitsStorage: SyncSplitsStorage - private let ruleBasedSegmentsStorage: RuleBasedSegmentsStorage + private var ruleBasedSegmentsStorage: RuleBasedSegmentsStorage private let splitChangeProcessor: SplitChangeProcessor private let ruleBasedSegmentsChangeProcessor: RuleBasedSegmentChangeProcessor private let splitConfig: SplitClientConfig @@ -202,11 +202,13 @@ class SplitsSyncHelper { ruleBasedSegmentsStorage.clear() } firstFetch = false + if splitsStorage.update(splitChange: splitChangeProcessor.process(targetingRulesChange.featureFlags)) { featureFlagsUpdated = true } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) + ruleBasedSegmentsStorage.segmentsInUse = splitsStorage.getSegmentsInUse() if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { rbsUpdated = true } diff --git a/Split/Localhost/LocalhostSplitsStorage.swift b/Split/Localhost/LocalhostSplitsStorage.swift index 707aeeb4a..70e41faee 100644 --- a/Split/Localhost/LocalhostSplitsStorage.swift +++ b/Split/Localhost/LocalhostSplitsStorage.swift @@ -9,12 +9,11 @@ import Foundation class LocalhostSplitsStorage: SplitsStorage { - var changeNumber: Int64 = -1 var updateTimestamp: Int64 = 1 var splitsFilterQueryString: String = "" var flagsSpec: String = "" - internal var segmentsInUse: Int64 = 0 + var segmentsInUse: Int64 = 0 private let inMemorySplits = ConcurrentDictionary() @@ -48,6 +47,10 @@ class LocalhostSplitsStorage: SplitsStorage { inMemorySplits.setValues(values) return true } + + func getSegmentsInUse() -> Int64 { + segmentsInUse + } func update(filterQueryString: String) { } diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 5aede0758..9a812c79d 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -101,22 +101,22 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a targeting rule update notification and return true if successful private func processTargetingRuleUpdate(notification: TargetingRuleUpdateNotification, - payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64) -> Bool { + payload: String, + compressionType: CompressionType, + previousChangeNumber: Int64) -> Bool { switch notification.type { case .splitUpdate: return processSplitUpdate(payload: payload, - compressionType: compressionType, - previousChangeNumber: previousChangeNumber, - changeNumber: notification.changeNumber) + compressionType: compressionType, + previousChangeNumber: previousChangeNumber, + changeNumber: notification.changeNumber) case .ruleBasedSegmentUpdate: return processRuleBasedSegmentUpdate(payload: payload, - compressionType: compressionType, - previousChangeNumber: previousChangeNumber, - changeNumber: notification.changeNumber) + compressionType: compressionType, + previousChangeNumber: previousChangeNumber, + changeNumber: notification.changeNumber) default: return false @@ -125,9 +125,9 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a split update notification private func processSplitUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { let split = try self.payloadDecoder.decode( payload: payload, @@ -157,9 +157,9 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a rule-based segment update notification private func processRuleBasedSegmentUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { let rbs = try self.ruleBasedSegmentsPayloadDecoder.decode( payload: payload, @@ -174,8 +174,8 @@ class SplitsUpdateWorker: UpdateWorker { let processedChange = ruleBasedSegmentsChangeProcessor.process(change) if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, - toRemove: processedChange.toRemove, - changeNumber: processedChange.changeNumber) { + toRemove: processedChange.toRemove, + changeNumber: processedChange.changeNumber) { self.synchronizer.notifyFeatureFlagsUpdated() } diff --git a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift index a9a11b165..3f91fb4bb 100644 --- a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift +++ b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift @@ -10,7 +10,7 @@ import Foundation protocol RuleBasedSegmentsStorage: RolloutDefinitionsCache { var changeNumber: Int64 { get } - var segmentsInUse: Int64 { get } + var segmentsInUse: Int64 { get set } func get(segmentName: String) -> RuleBasedSegment? func contains(segmentNames: Set) -> Bool @@ -26,7 +26,7 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { private(set) var changeNumber: Int64 = -1 - internal var segmentsInUse: Int64 = 0 + var segmentsInUse: Int64 = 0 init(persistentStorage: PersistentRuleBasedSegmentsStorage) { self.persistentStorage = persistentStorage @@ -68,7 +68,6 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { func update(toAdd: Set, toRemove: Set, changeNumber: Int64) -> Bool { - segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 self.changeNumber = changeNumber // Process diff --git a/Split/Storage/Splits/SplitsStorage.swift b/Split/Storage/Splits/SplitsStorage.swift index 8574846b7..f079fa111 100644 --- a/Split/Storage/Splits/SplitsStorage.swift +++ b/Split/Storage/Splits/SplitsStorage.swift @@ -10,6 +10,7 @@ import Foundation protocol SyncSplitsStorage: RolloutDefinitionsCache { func update(splitChange: ProcessedSplitChange) -> Bool + func getSegmentsInUse() -> Int64 } protocol SplitsStorage: SyncSplitsStorage { @@ -28,6 +29,7 @@ protocol SplitsStorage: SyncSplitsStorage { func getCount() -> Int func destroy() func forceParsing() + func getSegmentsInUse() -> Int64 } class DefaultSplitsStorage: SplitsStorage { @@ -36,7 +38,7 @@ class DefaultSplitsStorage: SplitsStorage { private var inMemorySplits: SynchronizedDictionary private var trafficTypes: SynchronizedDictionary private let flagSetsCache: FlagSetsCache - internal var segmentsInUse: Int64 = 0 + var segmentsInUse: Int64 = 0 private(set) var changeNumber: Int64 = -1 private(set) var updateTimestamp: Int64 = -1 @@ -121,6 +123,10 @@ class DefaultSplitsStorage: SplitsStorage { return inMemorySplits.count } + func getSegmentsInUse() -> Int64 { + segmentsInUse + } + private func processUpdated(splits: [Split], active: Bool) -> Bool { var cachedSplits = inMemorySplits.all var cachedTrafficTypes = trafficTypes.all @@ -277,6 +283,10 @@ class BackgroundSyncSplitsStorage: SyncSplitsStorage { func clear() { persistentStorage.clear() } + + func getSegmentsInUse() -> Int64 { + persistentStorage.getSegmentsInUse() ?? 0 + } } diff --git a/SplitTests/Fake/Storage/SplitsStorageStub.swift b/SplitTests/Fake/Storage/SplitsStorageStub.swift index 5fd1f82e9..1f1ffff1b 100644 --- a/SplitTests/Fake/Storage/SplitsStorageStub.swift +++ b/SplitTests/Fake/Storage/SplitsStorageStub.swift @@ -73,6 +73,10 @@ class SplitsStorageStub: SplitsStorage { updateSplitChangeCalled = true return splitsWereUpdated } + + func getSegmentsInUse() -> Int64 { + segmentsInUse + } var updateFlagsSpecCalled = false func update(flagsSpec: String) { From 8535526748579943b7b7b8131d1616c356bb4eec Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 21 Aug 2025 12:15:18 -0300 Subject: [PATCH 02/17] Logging info for customer --- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index d23ef3a17..21e9d7146 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -90,8 +90,10 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { // MARK: Important. This should be called before loadLocal() // MARK: Part of /memberships hits optimization if self.storageContainer.generalInfoStorage.getSegmentsInUse() == nil { + Logger.v("Force Parsing DB") splitsStorage.forceParsing() ruleBasedSegmentsStorage.forceParsing() + TimeChecker.logInterval("Time for Force Parsing", startTime: start) } // Load local From 3bc1f8d33fcda5e0d057644c64d424e0a81915c2 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 21 Aug 2025 12:26:14 -0300 Subject: [PATCH 03/17] Added condition to Force Parsing --- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 21e9d7146..a160b05f6 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -89,7 +89,7 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { // MARK: Important. This should be called before loadLocal() // MARK: Part of /memberships hits optimization - if self.storageContainer.generalInfoStorage.getSegmentsInUse() == nil { + if storageContainer.generalInfoStorage.getSegmentsInUse() == nil && storageContainer.splitsStorage.getAll().count > 0 { Logger.v("Force Parsing DB") splitsStorage.forceParsing() ruleBasedSegmentsStorage.forceParsing() From 299ca18e72e9c19837c7eda4b90206559b95137e Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 21 Aug 2025 12:28:43 -0300 Subject: [PATCH 04/17] Details --- .../Network/Streaming/SyncUpdateWorker.swift | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 9a812c79d..5aede0758 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -101,22 +101,22 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a targeting rule update notification and return true if successful private func processTargetingRuleUpdate(notification: TargetingRuleUpdateNotification, - payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64) -> Bool { + payload: String, + compressionType: CompressionType, + previousChangeNumber: Int64) -> Bool { switch notification.type { case .splitUpdate: return processSplitUpdate(payload: payload, - compressionType: compressionType, - previousChangeNumber: previousChangeNumber, - changeNumber: notification.changeNumber) + compressionType: compressionType, + previousChangeNumber: previousChangeNumber, + changeNumber: notification.changeNumber) case .ruleBasedSegmentUpdate: return processRuleBasedSegmentUpdate(payload: payload, - compressionType: compressionType, - previousChangeNumber: previousChangeNumber, - changeNumber: notification.changeNumber) + compressionType: compressionType, + previousChangeNumber: previousChangeNumber, + changeNumber: notification.changeNumber) default: return false @@ -125,9 +125,9 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a split update notification private func processSplitUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { let split = try self.payloadDecoder.decode( payload: payload, @@ -157,9 +157,9 @@ class SplitsUpdateWorker: UpdateWorker { /// Process a rule-based segment update notification private func processRuleBasedSegmentUpdate(payload: String, - compressionType: CompressionType, - previousChangeNumber: Int64, - changeNumber: Int64) -> Bool { + compressionType: CompressionType, + previousChangeNumber: Int64, + changeNumber: Int64) -> Bool { do { let rbs = try self.ruleBasedSegmentsPayloadDecoder.decode( payload: payload, @@ -174,8 +174,8 @@ class SplitsUpdateWorker: UpdateWorker { let processedChange = ruleBasedSegmentsChangeProcessor.process(change) if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, - toRemove: processedChange.toRemove, - changeNumber: processedChange.changeNumber) { + toRemove: processedChange.toRemove, + changeNumber: processedChange.changeNumber) { self.synchronizer.notifyFeatureFlagsUpdated() } From 5e259d68785478baff485c109799292b29d4981b Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Thu, 21 Aug 2025 16:50:43 -0300 Subject: [PATCH 05/17] Cached SegmentsInUse in Storage --- Split/FetcherEngine/Refresh/SplitsSyncHelper.swift | 2 +- Split/Network/Streaming/SyncUpdateWorker.swift | 1 - Split/Network/Sync/FeatureFlagsSynchronizer.swift | 2 +- Split/Storage/GeneralInfo/GeneralInfoStorage.swift | 14 ++++++++++++-- .../RuleBasedSegmentsStorage.swift | 1 + .../streaming/MySegmentUpdateTest.swift | 1 - 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index e65a86e36..206da0f60 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -208,7 +208,7 @@ class SplitsSyncHelper { } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) - ruleBasedSegmentsStorage.segmentsInUse = splitsStorage.getSegmentsInUse() + //ruleBasedSegmentsStorage.segmentsInUse = splitsStorage.getSegmentsInUse() if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { rbsUpdated = true } diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 5aede0758..e9c90f2c5 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -172,7 +172,6 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("RBS update received: \(change)") let processedChange = ruleBasedSegmentsChangeProcessor.process(change) - if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index a160b05f6..5bf27a5de 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -89,7 +89,7 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { // MARK: Important. This should be called before loadLocal() // MARK: Part of /memberships hits optimization - if storageContainer.generalInfoStorage.getSegmentsInUse() == nil && storageContainer.splitsStorage.getAll().count > 0 { + if storageContainer.generalInfoStorage.getSegmentsInUse() == nil && storageContainer.splitsStorage.changeNumber > -1 { Logger.v("Force Parsing DB") splitsStorage.forceParsing() ruleBasedSegmentsStorage.forceParsing() diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index 6193c4e0b..7fa133cee 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -26,6 +26,10 @@ protocol GeneralInfoStorage { class DefaultGeneralInfoStorage: GeneralInfoStorage { private let generalInfoDao: GeneralInfoDao + private var queue = DispatchQueue(label: "io.split.DefaultGeneralInfoStorage") + + // Part of Smart Pausing Optimization Feature + private var segmentsInUse: Int64? = nil init(generalInfoDao: GeneralInfoDao) { self.generalInfoDao = generalInfoDao @@ -84,10 +88,16 @@ class DefaultGeneralInfoStorage: GeneralInfoStorage { } func getSegmentsInUse() -> Int64? { - generalInfoDao.longValue(info: .segmentsInUse) + queue.async { [weak self] in + self?.segmentsInUse = self?.generalInfoDao.longValue(info: .segmentsInUse) + } + return segmentsInUse } func setSegmentsInUse(_ count: Int64) { - generalInfoDao.update(info: .segmentsInUse, longValue: count) + segmentsInUse = count + queue.async { [weak self] in + self?.generalInfoDao.update(info: .segmentsInUse, longValue: count) + } } } diff --git a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift index 3f91fb4bb..c81401b2b 100644 --- a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift +++ b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift @@ -68,6 +68,7 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { func update(toAdd: Set, toRemove: Set, changeNumber: Int64) -> Bool { + segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 self.changeNumber = changeNumber // Process diff --git a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift index 31e552aeb..ab14a7735 100644 --- a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift +++ b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift @@ -693,7 +693,6 @@ class MySegmentUpdateTest: XCTestCase { wait(for: [sdkUpdExp, sdkUpdMExp], timeout: 15) - // Hits are not asserted because tests will fail if expectations are not fulfilled XCTAssertEqual(4, syncSpy.forceMySegmentsSyncCount[userKey] ?? 0) XCTAssertEqual(4, syncSpy.forceMySegmentsSyncCount[userKeyM] ?? 0) From b7f0d8b557cc31b37f315432460a7b940e872251 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 11:57:30 -0300 Subject: [PATCH 06/17] Refactor of Storages and GeneralInfo --- Split/Api/SplitDatabaseHelper.swift | 11 +++-- .../Refresh/BackgroundSyncWorker.swift | 5 ++- .../Refresh/SplitBgSynchronizer.swift | 3 +- .../GeneralInfo/GeneralInfoStorage.swift | 7 ++- .../PersistentRuleBasedSegmentsStorage.swift | 11 ----- .../RuleBasedSegmentsStorage.swift | 44 ++++++++++--------- .../Splits/PersistentSplitsStorage.swift | 10 ----- Split/Storage/Splits/SplitsStorage.swift | 39 +++++++--------- .../Fake/Storage/GeneralInfoStorageMock.swift | 2 +- ...rsistentRuleBasedSegmentsStorageStub.swift | 9 ---- .../Storage/PersistentSplitsStorageStub.swift | 12 ----- .../streaming/MySegmentUpdateTest.swift | 1 + .../Splits/SplitsBgSyncWorkerTest.swift | 12 +++-- .../Storage/RuleBasedSegmentStorageTest.swift | 20 ++++----- SplitTests/Storage/SplitsStorageTests.swift | 30 ++++++------- .../SplitsStorageTrafficTypesTests.swift | 3 +- .../FeatureFlagsSynchronizerTest.swift | 34 +++++++++++--- 17 files changed, 116 insertions(+), 137 deletions(-) diff --git a/Split/Api/SplitDatabaseHelper.swift b/Split/Api/SplitDatabaseHelper.swift index 12eb27e9c..c30801b19 100644 --- a/Split/Api/SplitDatabaseHelper.swift +++ b/Split/Api/SplitDatabaseHelper.swift @@ -103,7 +103,8 @@ struct SplitDatabaseHelper { let flagSetsCache: FlagSetsCache = DefaultFlagSetsCache(setsInFilter: splitClientConfig.bySetsFilter()?.values.asSet()) let persistentSplitsStorage = DefaultPersistentSplitsStorage(database: splitDatabase) - let splitsStorage = openSplitsStorage(database: splitDatabase, flagSetsCache: flagSetsCache) + let generalInfoStorage = openGeneralInfoStorage(database: splitDatabase) + let splitsStorage = openSplitsStorage(database: splitDatabase, flagSetsCache: flagSetsCache, generalInfoStorage: generalInfoStorage) let persistentImpressionsStorage = openPersistentImpressionsStorage(database: splitDatabase) let impressionsStorage = openImpressionsStorage(persistentStorage: persistentImpressionsStorage) @@ -111,8 +112,6 @@ struct SplitDatabaseHelper { let persistentEventsStorage = openPersistentEventsStorage(database: splitDatabase) let eventsStorage = openEventsStorage(persistentStorage: persistentEventsStorage) - - let generalInfoStorage = openGeneralInfoStorage(database: splitDatabase) let mySegmentsStorage = openMySegmentsStorage(database: splitDatabase, generalInfoStorage: generalInfoStorage) let myLargeSegmentsStorage = openMyLargeSegmentsStorage(database: splitDatabase, generalInfoStorage: generalInfoStorage) @@ -133,7 +132,7 @@ struct SplitDatabaseHelper { generalInfoStorage: generalInfoStorage) let ruleBasedSegmentsStorage = DefaultRuleBasedSegmentsStorage( - persistentStorage: persistentRuleBasedSegmentsStorage) + persistentStorage: persistentRuleBasedSegmentsStorage, generalInfoStorage: generalInfoStorage) return SplitStorageContainer(splitDatabase: splitDatabase, splitsStorage: splitsStorage, @@ -175,9 +174,9 @@ struct SplitDatabaseHelper { } static func openSplitsStorage(database: SplitDatabase, - flagSetsCache: FlagSetsCache) -> SplitsStorage { + flagSetsCache: FlagSetsCache, generalInfoStorage: GeneralInfoStorage) -> SplitsStorage { return DefaultSplitsStorage(persistentSplitsStorage: openPersistentSplitsStorage(database: database), - flagSetsCache: flagSetsCache) + flagSetsCache: flagSetsCache, GeneralInfoStorage: generalInfoStorage) } static func openPersistentMySegmentsStorage(database: SplitDatabase) -> PersistentMySegmentsStorage { diff --git a/Split/FetcherEngine/Refresh/BackgroundSyncWorker.swift b/Split/FetcherEngine/Refresh/BackgroundSyncWorker.swift index 035c2a410..ce5e04709 100644 --- a/Split/FetcherEngine/Refresh/BackgroundSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/BackgroundSyncWorker.swift @@ -57,7 +57,8 @@ class BackgroundSplitsSyncWorker: BackgroundSyncWorker { splitChangeProcessor: SplitChangeProcessor, ruleBasedSegmentsChangeProcessor: RuleBasedSegmentChangeProcessor, cacheExpiration: Int64, - splitConfig: SplitClientConfig) { + splitConfig: SplitClientConfig, + generalInfoStorage: GeneralInfoStorage) { self.persistenSplitsStorage = persistentSplitsStorage self.persistentRuleBasedSegmentsStorage = persistentRuleBasedSegmentsStorage @@ -66,7 +67,7 @@ class BackgroundSplitsSyncWorker: BackgroundSyncWorker { self.cacheExpiration = cacheExpiration self.syncHelper = SplitsSyncHelper(splitFetcher: splitFetcher, splitsStorage: BackgroundSyncSplitsStorage(persistentSplitsStorage: persistentSplitsStorage), - ruleBasedSegmentsStorage: DefaultRuleBasedSegmentsStorage(persistentStorage: persistentRuleBasedSegmentsStorage), + ruleBasedSegmentsStorage: DefaultRuleBasedSegmentsStorage(persistentStorage: persistentRuleBasedSegmentsStorage, generalInfoStorage: generalInfoStorage), splitChangeProcessor: splitChangeProcessor, ruleBasedSegmentsChangeProcessor: ruleBasedSegmentsChangeProcessor, generalInfoStorage: nil, // Pass nil to disable proxy handling for background sync diff --git a/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift b/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift index 5f21f0c10..0cd4c02a7 100644 --- a/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift +++ b/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift @@ -198,7 +198,8 @@ struct BackgroundSyncExecutor { splitChangeProcessor: changeProcessor, ruleBasedSegmentsChangeProcessor: ruleBasedSegmentChangeProcessor, cacheExpiration: cacheExpiration, - splitConfig: SplitClientConfig()) + splitConfig: SplitClientConfig(), + generalInfoStorage: generalInfoStorage) let impressionsRecorder = DefaultHttpImpressionsRecorder(restClient: restClient, diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index 7fa133cee..4a0669336 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -88,13 +88,16 @@ class DefaultGeneralInfoStorage: GeneralInfoStorage { } func getSegmentsInUse() -> Int64? { - queue.async { [weak self] in - self?.segmentsInUse = self?.generalInfoDao.longValue(info: .segmentsInUse) + queue.sync { + if segmentsInUse == nil { + segmentsInUse = generalInfoDao.longValue(info: .segmentsInUse) + } } return segmentsInUse } func setSegmentsInUse(_ count: Int64) { + print(" **** \(count)") segmentsInUse = count queue.async { [weak self] in self?.generalInfoDao.update(info: .segmentsInUse, longValue: count) diff --git a/Split/Storage/RuleBasedSegments/PersistentRuleBasedSegmentsStorage.swift b/Split/Storage/RuleBasedSegments/PersistentRuleBasedSegmentsStorage.swift index 42de8af24..5facb7253 100644 --- a/Split/Storage/RuleBasedSegments/PersistentRuleBasedSegmentsStorage.swift +++ b/Split/Storage/RuleBasedSegments/PersistentRuleBasedSegmentsStorage.swift @@ -13,9 +13,6 @@ protocol PersistentRuleBasedSegmentsStorage { func update(toAdd: Set, toRemove: Set, changeNumber: Int64) func clear() func getChangeNumber() -> Int64 - - func getSegmentsInUse() -> Int64? - func setSegmentsInUse(_ segmentsInUse: Int64) } class DefaultPersistentRuleBasedSegmentsStorage: PersistentRuleBasedSegmentsStorage { @@ -58,12 +55,4 @@ class DefaultPersistentRuleBasedSegmentsStorage: PersistentRuleBasedSegmentsStor func getChangeNumber() -> Int64 { return generalInfoStorage.getRuleBasedSegmentsChangeNumber() } - - func getSegmentsInUse() -> Int64? { - generalInfoStorage.getSegmentsInUse() - } - - func setSegmentsInUse(_ segmentsInUse: Int64) { - generalInfoStorage.setSegmentsInUse(segmentsInUse) - } } diff --git a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift index c81401b2b..2e57ccbde 100644 --- a/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift +++ b/Split/Storage/RuleBasedSegments/RuleBasedSegmentsStorage.swift @@ -10,7 +10,6 @@ import Foundation protocol RuleBasedSegmentsStorage: RolloutDefinitionsCache { var changeNumber: Int64 { get } - var segmentsInUse: Int64 { get set } func get(segmentName: String) -> RuleBasedSegment? func contains(segmentNames: Set) -> Bool @@ -22,28 +21,28 @@ protocol RuleBasedSegmentsStorage: RolloutDefinitionsCache { class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { private var persistentStorage: PersistentRuleBasedSegmentsStorage + private var generalInfoStorage: GeneralInfoStorage private var inMemorySegments: ConcurrentDictionary private(set) var changeNumber: Int64 = -1 - - var segmentsInUse: Int64 = 0 - init(persistentStorage: PersistentRuleBasedSegmentsStorage) { + init(persistentStorage: PersistentRuleBasedSegmentsStorage, generalInfoStorage: GeneralInfoStorage) { self.persistentStorage = persistentStorage self.inMemorySegments = ConcurrentDictionary() + self.generalInfoStorage = generalInfoStorage } func loadLocal() { - segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 + var segmentsInUse: Int64 = generalInfoStorage.getSegmentsInUse() ?? 0 let snapshot = persistentStorage.getSnapshot() let active = snapshot.segments.filter { $0.status == .active } let archived = snapshot.segments.filter { $0.status == .archived } - _ = processToAdd(Set(active)) - _ = processToRemove(Set(archived)) + _ = processToAdd(Set(active), &segmentsInUse) + _ = processToRemove(Set(archived), &segmentsInUse) changeNumber = snapshot.changeNumber - persistentStorage.setSegmentsInUse(segmentsInUse) + generalInfoStorage.setSegmentsInUse(segmentsInUse) } func get(segmentName: String) -> RuleBasedSegment? { @@ -68,39 +67,40 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { func update(toAdd: Set, toRemove: Set, changeNumber: Int64) -> Bool { - segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 + var segmentsInUse = generalInfoStorage.getSegmentsInUse() ?? 0 self.changeNumber = changeNumber // Process - let addResult = processToAdd(toAdd) - let removeResult = processToRemove(toRemove) + let addResult = processToAdd(toAdd, &segmentsInUse) + let removeResult = processToRemove(toRemove, &segmentsInUse) // Update persistent storage persistentStorage.update(toAdd: toAdd, toRemove: toRemove, changeNumber: changeNumber) - persistentStorage.setSegmentsInUse(segmentsInUse) + generalInfoStorage.setSegmentsInUse(segmentsInUse) return addResult || removeResult } - private func processToAdd(_ toAdd: Set) -> Bool { // Process segments to add + private func processToAdd(_ toAdd: Set, _ segmentsInUse: inout Int64) -> Bool { // Process segments to add var result = false for segment in toAdd { if let segmentName = segment.name?.lowercased() { - updateSegmentsCount(segment) + segmentsInUse += updateSegmentsCount(segment) inMemorySegments.setValue(segment, forKey: segmentName) result = true } } + return result } - private func processToRemove(_ toRemove: Set) -> Bool { // Process segments to remove + private func processToRemove(_ toRemove: Set, _ segmentsInUse: inout Int64) -> Bool { // Process segments to remove var result = false for segment in toRemove { if let segmentName = segment.name?.lowercased(), inMemorySegments.value(forKey: segmentName) != nil { - updateSegmentsCount(segment) + segmentsInUse += updateSegmentsCount(segment) inMemorySegments.removeValue(forKey: segmentName) result = true } @@ -115,19 +115,19 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { } func forceParsing() { - segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 + var segmentsInUse = generalInfoStorage.getSegmentsInUse() ?? 0 let activeSegments = persistentStorage.getSnapshot().segments.filter { $0.status == .active } for i in 0.. RuleBasedSegment? { @@ -135,12 +135,16 @@ class DefaultRuleBasedSegmentsStorage: RuleBasedSegmentsStorage { return parsedSegment } - fileprivate func updateSegmentsCount(_ segment: RuleBasedSegment) { + fileprivate func updateSegmentsCount(_ segment: RuleBasedSegment) -> Int64 { + var segmentsInUse: Int64 = 0 + if let segmentName = segment.name?.lowercased(), segment.status == .active, inMemorySegments.value(forKey: segmentName) == nil, StorageHelper.usesSegments(segment.conditions) { segmentsInUse += 1 } else if inMemorySegments.value(forKey: segment.name?.lowercased() ?? "") != nil, segment.status != .active, StorageHelper.usesSegments(segment.conditions) { segmentsInUse -= 1 } + + return segmentsInUse } #if DEBUG diff --git a/Split/Storage/Splits/PersistentSplitsStorage.swift b/Split/Storage/Splits/PersistentSplitsStorage.swift index e6faf2567..b082b5825 100644 --- a/Split/Storage/Splits/PersistentSplitsStorage.swift +++ b/Split/Storage/Splits/PersistentSplitsStorage.swift @@ -12,12 +12,10 @@ protocol PersistentSplitsStorage { func update(splitChange: ProcessedSplitChange) func update(split: Split) func update(bySetsFilter: SplitFilter?) - func update(segmentsInUse: Int64) func getBySetsFilter() -> SplitFilter? func getSplitsSnapshot() -> SplitsSnapshot func getChangeNumber() -> Int64 func getUpdateTimestamp() -> Int64 - func getSegmentsInUse() -> Int64? func getAll() -> [Split] func delete(splitNames: [String]) func clear() @@ -51,10 +49,6 @@ class DefaultPersistentSplitsStorage: PersistentSplitsStorage { func update(flagsSpec: String) { generalInfoDao.update(info: .flagsSpec, stringValue: flagsSpec) } - - func update(segmentsInUse: Int64) { - generalInfoDao.update(info: .segmentsInUse, longValue: segmentsInUse) - } func getFilterQueryString() -> String { return generalInfoDao.stringValue(info: .splitsFilterQueryString) ?? "" @@ -63,10 +57,6 @@ class DefaultPersistentSplitsStorage: PersistentSplitsStorage { func getFlagsSpec() -> String { return generalInfoDao.stringValue(info: .flagsSpec) ?? "" } - - func getSegmentsInUse() -> Int64? { - generalInfoDao.longValue(info: .segmentsInUse) - } func update(bySetsFilter filter: SplitFilter?) { guard let filter = filter else { diff --git a/Split/Storage/Splits/SplitsStorage.swift b/Split/Storage/Splits/SplitsStorage.swift index f079fa111..8ef9d41a7 100644 --- a/Split/Storage/Splits/SplitsStorage.swift +++ b/Split/Storage/Splits/SplitsStorage.swift @@ -10,13 +10,11 @@ import Foundation protocol SyncSplitsStorage: RolloutDefinitionsCache { func update(splitChange: ProcessedSplitChange) -> Bool - func getSegmentsInUse() -> Int64 } protocol SplitsStorage: SyncSplitsStorage { var changeNumber: Int64 { get } var updateTimestamp: Int64 { get } - var segmentsInUse: Int64 { get } func loadLocal() func get(name: String) -> Split? @@ -29,7 +27,6 @@ protocol SplitsStorage: SyncSplitsStorage { func getCount() -> Int func destroy() func forceParsing() - func getSegmentsInUse() -> Int64 } class DefaultSplitsStorage: SplitsStorage { @@ -38,21 +35,22 @@ class DefaultSplitsStorage: SplitsStorage { private var inMemorySplits: SynchronizedDictionary private var trafficTypes: SynchronizedDictionary private let flagSetsCache: FlagSetsCache - var segmentsInUse: Int64 = 0 + private let generalInfoStorage: GeneralInfoStorage private(set) var changeNumber: Int64 = -1 private(set) var updateTimestamp: Int64 = -1 init(persistentSplitsStorage: PersistentSplitsStorage, - flagSetsCache: FlagSetsCache) { + flagSetsCache: FlagSetsCache, + GeneralInfoStorage: GeneralInfoStorage) { self.persistentStorage = persistentSplitsStorage self.inMemorySplits = SynchronizedDictionary() self.trafficTypes = SynchronizedDictionary() self.flagSetsCache = flagSetsCache + self.generalInfoStorage = GeneralInfoStorage } func loadLocal() { - segmentsInUse = persistentStorage.getSegmentsInUse() ?? 0 let snapshot = persistentStorage.getSplitsSnapshot() let active = snapshot.splits.filter { $0.status == .active } let archived = snapshot.splits.filter { $0.status == .archived } @@ -123,15 +121,12 @@ class DefaultSplitsStorage: SplitsStorage { return inMemorySplits.count } - func getSegmentsInUse() -> Int64 { - segmentsInUse - } - private func processUpdated(splits: [Split], active: Bool) -> Bool { var cachedSplits = inMemorySplits.all var cachedTrafficTypes = trafficTypes.all var splitsUpdated = false var splitsRemoved = false + var segmentsInUse: Int64 = generalInfoStorage.getSegmentsInUse() ?? 0 for split in splits { @@ -153,7 +148,7 @@ class DefaultSplitsStorage: SplitsStorage { } // Smart Pausing optimization - updateSegmentsCount(split: split) + segmentsInUse += updateSegmentsCount(split: split) if loadedSplit != nil, let oldTrafficType = loadedSplit?.trafficTypeName { // Must decrease old traffic type count if a feature flag is updated or removed @@ -180,7 +175,7 @@ class DefaultSplitsStorage: SplitsStorage { } inMemorySplits.setValues(cachedSplits) trafficTypes.setValues(cachedTrafficTypes) - persistentStorage.update(segmentsInUse: segmentsInUse) // Ensure count of Flags with Segments (for optimization feature) + generalInfoStorage.setSegmentsInUse(segmentsInUse) // Ensure count of Flags with Segments (for optimization feature) return splitsUpdated || splitsRemoved } @@ -231,33 +226,35 @@ class DefaultSplitsStorage: SplitsStorage { } func forceParsing() { // Parse all Splits - segmentsInUse = 0 + var segmentsInUse: Int64 = 0 let activeSplits = persistentStorage.getSplitsSnapshot().splits.filter( { $0.status == .active } ) if activeSplits.count > 0 { for i in 0...activeSplits.count-1 { guard let splitName = activeSplits[i].name else { continue } let parsedSplit = parseSplit(activeSplits[i]) - updateSegmentsCount(split: parsedSplit) + segmentsInUse += updateSegmentsCount(split: parsedSplit) inMemorySplits.setValue(parsedSplit, forKey: splitName) } } - persistentStorage.update(segmentsInUse: segmentsInUse) + generalInfoStorage.setSegmentsInUse(segmentsInUse) } - func updateSegmentsCount(split: Split) { // Keep count of Flags with Segments (used to optimize "/memberships" hits) - guard let splitName = split.name else { return } + func updateSegmentsCount(split: Split) -> Int64 { // Keep count of Flags with Segments (used to optimize "/memberships" hits) + guard let splitName = split.name else { return 0 } if inMemorySplits.value(forKey: splitName) == nil, split.status == .active { // If new Split and active if StorageHelper.usesSegments(split.conditions ?? []) { - segmentsInUse += 1 + return 1 } } else if inMemorySplits.value(forKey: splitName) != nil && split.status != .active { // If known Split and archived if StorageHelper.usesSegments(split.conditions ?? []) { - segmentsInUse -= 1 + return -1 } } + + return 0 } #if DEBUG @@ -283,10 +280,6 @@ class BackgroundSyncSplitsStorage: SyncSplitsStorage { func clear() { persistentStorage.clear() } - - func getSegmentsInUse() -> Int64 { - persistentStorage.getSegmentsInUse() ?? 0 - } } diff --git a/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift b/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift index de21a1670..15e3eab98 100644 --- a/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift +++ b/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift @@ -10,7 +10,7 @@ class GeneralInfoStorageMock: GeneralInfoStorage { var flagsSpec = "" var ruleBasedSegmentsChangeNumber: Int64 = -1 var lastProxyUpdateTimestamp: Int64 = 0 - var segmentsInUse: Int64 = 0 + var segmentsInUse: Int64? = nil func getUpdateTimestamp() -> Int64 { return updateTimestamp diff --git a/SplitTests/Fake/Storage/PersistentRuleBasedSegmentsStorageStub.swift b/SplitTests/Fake/Storage/PersistentRuleBasedSegmentsStorageStub.swift index 2c8fb9138..0ffeee907 100644 --- a/SplitTests/Fake/Storage/PersistentRuleBasedSegmentsStorageStub.swift +++ b/SplitTests/Fake/Storage/PersistentRuleBasedSegmentsStorageStub.swift @@ -59,13 +59,4 @@ class PersistentRuleBasedSegmentsStorageStub: PersistentRuleBasedSegmentsStorage changeNumberCalled = true return delegate?.getChangeNumber() ?? -1 } - - var segmentsInUse: Int64 = 0 - func getSegmentsInUse() -> Int64? { - segmentsInUse - } - - func setSegmentsInUse(_ segmentsInUse: Int64) { - self.segmentsInUse = segmentsInUse - } } diff --git a/SplitTests/Fake/Storage/PersistentSplitsStorageStub.swift b/SplitTests/Fake/Storage/PersistentSplitsStorageStub.swift index 17454878c..a1bfd7550 100644 --- a/SplitTests/Fake/Storage/PersistentSplitsStorageStub.swift +++ b/SplitTests/Fake/Storage/PersistentSplitsStorageStub.swift @@ -21,7 +21,6 @@ class PersistentSplitsStorageStub: PersistentSplitsStorage { var getAllCalled = false var updateCalled = false - var getSegmentsInUseCalled = false var deleteCalled = false var clearCalled = false var closeCalled = false @@ -98,15 +97,4 @@ class PersistentSplitsStorageStub: PersistentSplitsStorage { getBySetsFilterCalled = false return nil } - - var segmentsInUse: Int64 = 0 - func update(segmentsInUse: Int64) { - self.segmentsInUse = segmentsInUse - delegate?.update(segmentsInUse: segmentsInUse) - } - - func getSegmentsInUse() -> Int64? { - getSegmentsInUseCalled = true - return segmentsInUse - } } diff --git a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift index ab14a7735..cccb70ba2 100644 --- a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift +++ b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift @@ -467,6 +467,7 @@ class MySegmentUpdateTest: XCTestCase { if request.url.absoluteString.contains("/memberships") { segmentsHit.fulfill() membershipsHit += 1 + print("HITTING MEMBERSHIPS") return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) } diff --git a/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift b/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift index a9c8d0889..b3e57df84 100644 --- a/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift +++ b/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift @@ -19,6 +19,7 @@ class SplitsBgSyncWorkerTest: XCTestCase { var splitChangeProcessor: SplitChangeProcessorStub! var ruleBasedSegmentChangeProcessor: RuleBasedSegmentChangeProcessorStub! var splitsSyncWorker: BackgroundSyncWorker! + var generalInfoStorage: GeneralInfoStorageMock! override func setUp() { splitFetcher = HttpSplitFetcherStub() @@ -38,7 +39,8 @@ class SplitsBgSyncWorkerTest: XCTestCase { splitChangeProcessor: splitChangeProcessor, ruleBasedSegmentsChangeProcessor: ruleBasedSegmentChangeProcessor, cacheExpiration: 100, - splitConfig: SplitClientConfig()) + splitConfig: SplitClientConfig(), + generalInfoStorage: generalInfoStorage) let change = SplitChange(splits: [], since: 200, till: 200) splitFetcher.splitChanges = [TargetingRulesChange(featureFlags: change)] @@ -57,7 +59,8 @@ class SplitsBgSyncWorkerTest: XCTestCase { splitChangeProcessor: splitChangeProcessor, ruleBasedSegmentsChangeProcessor: ruleBasedSegmentChangeProcessor, cacheExpiration: 100, - splitConfig: SplitClientConfig()) + splitConfig: SplitClientConfig(), + generalInfoStorage: generalInfoStorage) splitFetcher.httpError = HttpError.clientRelated(code: -1, internalCode: -1) @@ -76,8 +79,9 @@ class SplitsBgSyncWorkerTest: XCTestCase { splitChangeProcessor: splitChangeProcessor, ruleBasedSegmentsChangeProcessor: ruleBasedSegmentChangeProcessor, cacheExpiration: 2000, - splitConfig: SplitClientConfig()) - + splitConfig: SplitClientConfig(), + generalInfoStorage: generalInfoStorage) + let change = SplitChange(splits: [], since: 200, till: 200) splitStorage.updateTimestamp = Int64(Date().timeIntervalSince1970) - Int64(expiration / 2) // Non Expired cache splitFetcher.splitChanges = [TargetingRulesChange(featureFlags: change)] diff --git a/SplitTests/Storage/RuleBasedSegmentStorageTest.swift b/SplitTests/Storage/RuleBasedSegmentStorageTest.swift index f2fa32185..249339629 100644 --- a/SplitTests/Storage/RuleBasedSegmentStorageTest.swift +++ b/SplitTests/Storage/RuleBasedSegmentStorageTest.swift @@ -15,11 +15,10 @@ class RuleBasedSegmentStorageTest: XCTestCase { private var persistentStorageStub: PersistentRuleBasedSegmentsStorageStub! private var ruleBasedSegmentsStorage: DefaultRuleBasedSegmentsStorage! private var noLoadedRbs: DefaultRuleBasedSegmentsStorage? + private var generalInfoStorage: GeneralInfoStorageMock! override func setUp() { - ruleBasedSegmentsStorage = DefaultRuleBasedSegmentsStorage( - persistentStorage: createPersistentStorageStub() - ) + ruleBasedSegmentsStorage = DefaultRuleBasedSegmentsStorage(persistentStorage: createPersistentStorageStub(), generalInfoStorage: generalInfoStorage) ruleBasedSegmentsStorage.loadLocal() } @@ -30,9 +29,7 @@ class RuleBasedSegmentStorageTest: XCTestCase { } func testLazyParsing() { - noLoadedRbs = DefaultRuleBasedSegmentsStorage( - persistentStorage: createPersistentStorageStub(isParsed: false) - ) + noLoadedRbs = DefaultRuleBasedSegmentsStorage(persistentStorage: createPersistentStorageStub(isParsed: false), generalInfoStorage: generalInfoStorage) noLoadedRbs?.loadLocal() @@ -71,7 +68,7 @@ class RuleBasedSegmentStorageTest: XCTestCase { let customMock = MockPersistentRuleBasedSegmentsStorage() let persistentStorageStub = PersistentRuleBasedSegmentsStorageStub(delegate: customMock) let storage = DefaultRuleBasedSegmentsStorage( - persistentStorage: persistentStorageStub + persistentStorage: persistentStorageStub, generalInfoStorage: generalInfoStorage ) storage.loadLocal() @@ -110,9 +107,8 @@ class RuleBasedSegmentStorageTest: XCTestCase { ) persistentStorageStub = PersistentRuleBasedSegmentsStorageStub(delegate: customMock) - let storage = DefaultRuleBasedSegmentsStorage( - persistentStorage: persistentStorageStub - ) + let storage = DefaultRuleBasedSegmentsStorage(persistentStorage: persistentStorageStub, generalInfoStorage: generalInfoStorage) + storage.loadLocal() // Verify only active segments are loaded @@ -303,7 +299,7 @@ class RuleBasedSegmentStorageTest: XCTestCase { // 1. Counter should be 3 (ignore the other matcherTypes) _ = ruleBasedSegmentsStorage.update(toAdd: Set([segment1, segment2, segment3, segment4, segment5]), toRemove: [], changeNumber: 123) - XCTAssertEqual(ruleBasedSegmentsStorage.segmentsInUse, 3) + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 3) // 2 segment1.status = .archived // Archive of Segments with other matcherTypes should be ignored.. @@ -311,7 +307,7 @@ class RuleBasedSegmentStorageTest: XCTestCase { segment3.status = .archived _ = ruleBasedSegmentsStorage.update(toAdd: Set([]), toRemove: [segment1, segment2, segment3], changeNumber: 1230) - XCTAssertEqual(ruleBasedSegmentsStorage.segmentsInUse, 1) + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 1) } diff --git a/SplitTests/Storage/SplitsStorageTests.swift b/SplitTests/Storage/SplitsStorageTests.swift index b7ca0a969..dcb418b32 100644 --- a/SplitTests/Storage/SplitsStorageTests.swift +++ b/SplitTests/Storage/SplitsStorageTests.swift @@ -20,11 +20,12 @@ class SplitsStorageTest: XCTestCase { var persistentStorage: PersistentSplitsStorageStub! var splitsStorage: SplitsStorage! var noLoadedStorage: DefaultSplitsStorage? + var generalInfoStorage: GeneralInfoStorageMock! override func setUp() { persistentStorage = PersistentSplitsStorageStub() flagSetsCache = FlagSetsCacheMock() - splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistentStorage, flagSetsCache: flagSetsCache) + splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistentStorage, flagSetsCache: flagSetsCache, GeneralInfoStorage: generalInfoStorage) } func testNoLocalLoaded() { @@ -39,9 +40,7 @@ class SplitsStorageTest: XCTestCase { } func testLazyParsing() { - noLoadedStorage = DefaultSplitsStorage( - persistentSplitsStorage: createPersistentStorageStub(isParsed: false), flagSetsCache: FlagSetsCacheMock() - ) + noLoadedStorage = DefaultSplitsStorage(persistentSplitsStorage: createPersistentStorageStub(isParsed: false), flagSetsCache: FlagSetsCacheMock(), GeneralInfoStorage: generalInfoStorage) noLoadedStorage?.loadLocal() @@ -279,14 +278,12 @@ class SplitsStorageTest: XCTestCase { let flagSetsCache = FlagSetsCacheMock() flagSetsCache.setsInFilter = ["set1", "set2", "set3"] - splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistentStorage, flagSetsCache: flagSetsCache) - persistentStorage.snapshot = getTestSnapshot(count: 3, - sets: [ - ["set1", "set2"], - ["set1"], - ["set3"] - ] - ) + splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistentStorage, flagSetsCache: flagSetsCache, GeneralInfoStorage: GeneralInfoStorageMock()) + persistentStorage.snapshot = getTestSnapshot(count: 3, sets: [ + ["set1", "set2"], + ["set1"], + ["set3"] + ]) splitsStorage.loadLocal() @@ -326,7 +323,7 @@ class SplitsStorageTest: XCTestCase { splitsStorage.loadLocal() // 1. Check Segments count is in 0 - XCTAssertEqual(splitsStorage.segmentsInUse, 0) + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 0) let splitNotUsingSegments = newSplit(name: "added") @@ -336,9 +333,8 @@ class SplitsStorageTest: XCTestCase { changeNumber: 999, updateTimestamp: 888) _ = splitsStorage.update(splitChange: processedChange) - XCTAssertEqual(splitsStorage.segmentsInUse, 5) // One should have been ignored, so 5 + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 5) // One should have been ignored, so 5 XCTAssertTrue(persistentStorage.updateCalled) - XCTAssertTrue(persistentStorage.getSegmentsInUseCalled) // 3. Add 2 previously added (should be ignored by the counter), and a new one processedChange = ProcessedSplitChange(activeSplits: [split, split2, split6], @@ -346,7 +342,7 @@ class SplitsStorageTest: XCTestCase { changeNumber: 9999, updateTimestamp: 8888) _ = splitsStorage.update(splitChange: processedChange) - XCTAssertEqual(splitsStorage.segmentsInUse, 6) // So, count should be 6 + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 6) // So, count should be 6 // 4. Remove 3 (one not using segments) split2.status = .archived @@ -357,7 +353,7 @@ class SplitsStorageTest: XCTestCase { changeNumber: 99999, updateTimestamp: 88888) _ = splitsStorage.update(splitChange: processedChange) - XCTAssertEqual(splitsStorage.segmentsInUse, 4) // So, count should be 4 + XCTAssertEqual(generalInfoStorage.getSegmentsInUse(), 4) // So, count should be 4 } func testUnsupportedMatcherHasDefaultCondition() { diff --git a/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift b/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift index 6cd9e98ab..1d7fd3c50 100644 --- a/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift +++ b/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift @@ -14,6 +14,7 @@ class SplitsStorageTrafficTypesTests: XCTestCase { var splitsStorage: SplitsStorage! var flagSetsCache: FlagSetsCacheMock! + var generalInfoStorage: GeneralInfoStorageMock! override func setUp() { @@ -26,7 +27,7 @@ class SplitsStorageTrafficTypesTests: XCTestCase { flagSetsCache = FlagSetsCacheMock() persistent.snapshot = SplitsSnapshot(changeNumber: 1, splits: splits, updateTimestamp: 100) - splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistent, flagSetsCache: flagSetsCache) + splitsStorage = DefaultSplitsStorage(persistentSplitsStorage: persistent, flagSetsCache: flagSetsCache, GeneralInfoStorage: generalInfoStorage) splitsStorage.loadLocal() } diff --git a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift index a4d816e4a..c53f7e2fa 100644 --- a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift +++ b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift @@ -18,6 +18,7 @@ class FeatureFlagsSynchronizerTest: XCTestCase { var splitsSyncWorker: RetryableSyncWorkerStub! var periodicSplitsSyncWorker: PeriodicSyncWorkerStub! var persistentSplitsStorage: PersistentSplitsStorageStub! + var database: SplitDatabase! var splitsStorage: SplitsStorageStub! var ruleBasedSegmentsStorage: RuleBasedSegmentsStorageStub! @@ -35,8 +36,8 @@ class FeatureFlagsSynchronizerTest: XCTestCase { override func setUp() { synchronizer = buildSynchronizer() } - - func buildSynchronizer(syncEnabled: Bool = true, splitFilters: [SplitFilter]? = nil) -> FeatureFlagsSynchronizer { + + func buildSynchronizer(syncEnabled: Bool = true, splitFilters: [SplitFilter]? = nil, generalInfoStorage: GeneralInfoStorageMock? = nil, Database: SplitDatabase? = nil) -> FeatureFlagsSynchronizer { eventsManager = SplitEventsManagerStub() persistentSplitsStorage = PersistentSplitsStorageStub() @@ -49,13 +50,22 @@ class FeatureFlagsSynchronizerTest: XCTestCase { splitsStorage = SplitsStorageStub() ruleBasedSegmentsStorage = RuleBasedSegmentsStorageStub() broadcasterChannel = SyncEventBroadcasterStub() - generalInfoStorage = GeneralInfoStorageMock() + + if generalInfoStorage == nil { + self.generalInfoStorage = GeneralInfoStorageMock() + } + + if Database == nil { + self.database = TestingHelper.createTestDatabase(name: "pepe") + } + telemetryStorageStub = TelemetryStorageStub() _ = splitsStorage.update(splitChange: ProcessedSplitChange(activeSplits: [], archivedSplits: [], changeNumber: 100, updateTimestamp: 100)) - generalInfoStorage.setFlagSpec(flagsSpec: "1.2") + + self.generalInfoStorage.setFlagSpec(flagsSpec: "1.2") - let storageContainer = SplitStorageContainer(splitDatabase: TestingHelper.createTestDatabase(name: "pepe"), + let storageContainer = SplitStorageContainer(splitDatabase: database, splitsStorage: splitsStorage, persistentSplitsStorage: persistentSplitsStorage, impressionsStorage: ImpressionsStorageStub(), @@ -71,7 +81,7 @@ class FeatureFlagsSynchronizerTest: XCTestCase { flagSetsCache: FlagSetsCacheMock(), persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), hashedImpressionsStorage: HashedImpressionsStorageMock(), - generalInfoStorage: generalInfoStorage, + generalInfoStorage: self.generalInfoStorage, ruleBasedSegmentsStorage: ruleBasedSegmentsStorage, persistentRuleBasedSegmentsStorage: PersistentRuleBasedSegmentsStorageStub()) @@ -265,6 +275,7 @@ class FeatureFlagsSynchronizerTest: XCTestCase { ThreadUtils.delay(seconds: 0.5) + XCTAssertTrue(persistentSplitsStorage.clearCalled) XCTAssertTrue(persistentSplitsStorage.clearCalled) XCTAssertEqual(1, broadcasterChannel.pushedEvents.filter { $0 == .splitLoadedFromCache }.count) } @@ -340,4 +351,15 @@ class FeatureFlagsSynchronizerTest: XCTestCase { XCTAssertTrue(sw2.stopCalled) XCTAssertEqual(0, updateWorkerCatalog.count) } + + func testForceParcing() { + var generalInfoStorage = GeneralInfoStorageMock() + + synchronizer = buildSynchronizer(generalInfoStorage: GeneralInfoStorageMock()) // SegmentsInUse = nil, changeNumber = 100 + synchronizer.load() + + ThreadUtils.delay(seconds: 2) + + XCTAssertTrue(splitsStorage.forceReparsingCalled) + } } From 6d5c0a7192ea6d0e03b28da588f5b90bdff849a6 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 12:08:00 -0300 Subject: [PATCH 07/17] Crashes fixed --- SplitTests/Storage/RuleBasedSegmentStorageTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/Storage/RuleBasedSegmentStorageTest.swift b/SplitTests/Storage/RuleBasedSegmentStorageTest.swift index 249339629..4dfca3738 100644 --- a/SplitTests/Storage/RuleBasedSegmentStorageTest.swift +++ b/SplitTests/Storage/RuleBasedSegmentStorageTest.swift @@ -15,7 +15,7 @@ class RuleBasedSegmentStorageTest: XCTestCase { private var persistentStorageStub: PersistentRuleBasedSegmentsStorageStub! private var ruleBasedSegmentsStorage: DefaultRuleBasedSegmentsStorage! private var noLoadedRbs: DefaultRuleBasedSegmentsStorage? - private var generalInfoStorage: GeneralInfoStorageMock! + private var generalInfoStorage = GeneralInfoStorageMock() override func setUp() { ruleBasedSegmentsStorage = DefaultRuleBasedSegmentsStorage(persistentStorage: createPersistentStorageStub(), generalInfoStorage: generalInfoStorage) From 8a110b2b99c25dfe2d616c2dc6407ad8e90c2fc0 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 12:15:24 -0300 Subject: [PATCH 08/17] Crash on unit tests fixed --- SplitTests/Storage/SplitsStorageTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SplitTests/Storage/SplitsStorageTests.swift b/SplitTests/Storage/SplitsStorageTests.swift index dcb418b32..f75fd8ad2 100644 --- a/SplitTests/Storage/SplitsStorageTests.swift +++ b/SplitTests/Storage/SplitsStorageTests.swift @@ -20,7 +20,7 @@ class SplitsStorageTest: XCTestCase { var persistentStorage: PersistentSplitsStorageStub! var splitsStorage: SplitsStorage! var noLoadedStorage: DefaultSplitsStorage? - var generalInfoStorage: GeneralInfoStorageMock! + var generalInfoStorage = GeneralInfoStorageMock() override func setUp() { persistentStorage = PersistentSplitsStorageStub() From 706505124bf5e8155b3af6a5edb9f4e3707884f4 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 12:34:13 -0300 Subject: [PATCH 09/17] Crash on unit tests fixed --- Split/Storage/GeneralInfo/GeneralInfoStorage.swift | 2 +- SplitTests/Storage/SplitsStorageTrafficTypesTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index 4a0669336..b47888cb8 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -89,7 +89,7 @@ class DefaultGeneralInfoStorage: GeneralInfoStorage { func getSegmentsInUse() -> Int64? { queue.sync { - if segmentsInUse == nil { + if segmentsInUse == nil { // This happens just on start segmentsInUse = generalInfoDao.longValue(info: .segmentsInUse) } } diff --git a/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift b/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift index 1d7fd3c50..39228a116 100644 --- a/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift +++ b/SplitTests/Storage/SplitsStorageTrafficTypesTests.swift @@ -14,7 +14,7 @@ class SplitsStorageTrafficTypesTests: XCTestCase { var splitsStorage: SplitsStorage! var flagSetsCache: FlagSetsCacheMock! - var generalInfoStorage: GeneralInfoStorageMock! + var generalInfoStorage = GeneralInfoStorageMock() override func setUp() { From fa5ea8650692b36cbff9996e325142198ec8d8a4 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 13:26:51 -0300 Subject: [PATCH 10/17] Removed testing log on production code --- Split/Storage/GeneralInfo/GeneralInfoStorage.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index b47888cb8..416c3605f 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -97,7 +97,6 @@ class DefaultGeneralInfoStorage: GeneralInfoStorage { } func setSegmentsInUse(_ count: Int64) { - print(" **** \(count)") segmentsInUse = count queue.async { [weak self] in self?.generalInfoDao.update(info: .segmentsInUse, longValue: count) From fcd669b524df4d66b3f22afba933fc0f4ba56616 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 13:57:13 -0300 Subject: [PATCH 11/17] Queue priority updated --- Split/Storage/GeneralInfo/GeneralInfoStorage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index 416c3605f..be1c6934f 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -26,7 +26,7 @@ protocol GeneralInfoStorage { class DefaultGeneralInfoStorage: GeneralInfoStorage { private let generalInfoDao: GeneralInfoDao - private var queue = DispatchQueue(label: "io.split.DefaultGeneralInfoStorage") + private var queue = DispatchQueue(label: "io.split.DefaultGeneralInfoStorage", qos: .userInitiated) // Part of Smart Pausing Optimization Feature private var segmentsInUse: Int64? = nil From f8c0c6702f6fb1fcb7fadc93eef6406c955c1f74 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 15:23:22 -0300 Subject: [PATCH 12/17] Update Split/FetcherEngine/Refresh/SplitsSyncHelper.swift Co-authored-by: gthea --- Split/FetcherEngine/Refresh/SplitsSyncHelper.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift index 206da0f60..d1eaee4e8 100644 --- a/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift +++ b/Split/FetcherEngine/Refresh/SplitsSyncHelper.swift @@ -208,7 +208,6 @@ class SplitsSyncHelper { } let processedChange = ruleBasedSegmentsChangeProcessor.process(targetingRulesChange.ruleBasedSegments) - //ruleBasedSegmentsStorage.segmentsInUse = splitsStorage.getSegmentsInUse() if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { rbsUpdated = true } From c3d108c518dfbdb296b41b0a09b4c08594e84ff3 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 15:29:17 -0300 Subject: [PATCH 13/17] Update GeneralInfoStorage.swift --- Split/Storage/GeneralInfo/GeneralInfoStorage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift index be1c6934f..416c3605f 100644 --- a/Split/Storage/GeneralInfo/GeneralInfoStorage.swift +++ b/Split/Storage/GeneralInfo/GeneralInfoStorage.swift @@ -26,7 +26,7 @@ protocol GeneralInfoStorage { class DefaultGeneralInfoStorage: GeneralInfoStorage { private let generalInfoDao: GeneralInfoDao - private var queue = DispatchQueue(label: "io.split.DefaultGeneralInfoStorage", qos: .userInitiated) + private var queue = DispatchQueue(label: "io.split.DefaultGeneralInfoStorage") // Part of Smart Pausing Optimization Feature private var segmentsInUse: Int64? = nil From 39530cff822753cadbe2a95409c794d3bc93878f Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 17:01:18 -0300 Subject: [PATCH 14/17] Tests fixed --- Split/Events/SplitEventsManager.swift | 3 ++- .../Sync/SplitSdkUpdatePollingTest.swift | 25 ++++++------------- .../streaming/MySegmentUpdateTest.swift | 10 +++++--- .../streaming/StreamingAuthFail4xxTest.swift | 15 ++++++----- .../streaming/TelemetryIntegrationTest.swift | 5 ++-- .../Splits/SplitsBgSyncWorkerTest.swift | 2 +- 6 files changed, 26 insertions(+), 34 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index b66564a2e..949154082 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -148,6 +148,7 @@ class DefaultSplitEventsManager: SplitEventsManager { self.triggered.append(event) switch event { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: + print("*********** EVENT !! **************\n\(event)\n*********** EVENT !! **************") if isTriggered(external: .sdkReady) { trigger(event: .sdkUpdated) continue @@ -181,7 +182,7 @@ class DefaultSplitEventsManager: SplitEventsManager { var triggered = false dataAccessQueue.sync { if let times = executionTimes[event.toString()] { - triggered = (times == 0) + triggered = (times == 0) } else { triggered = false } diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 9a37c6761..3f5c6f32c 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -60,7 +60,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { } else if index == self.spExp.count { self.spExp[index - 1].fulfill() } - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptySplitChanges(since: 99999999, till: 99999999).utf8)) + let json = IntegrationHelper.loadSplitChangeFileJson(name: "splitschanges_no_segments", sourceClass: IntegrationHelper()) + return TestDispatcherResponse(code: 200, data: Data(json!.utf8)) } if request.isMySegmentsEndpoint() { @@ -76,7 +77,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { json = IntegrationHelper.buildSegments(regular: mySegments) return TestDispatcherResponse(code: 200, data: Data(json.utf8)) } - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.mySegments(names: ["", ""]).utf8)) } if request.isAuthEndpoint() { @@ -93,7 +94,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { return TestDispatcherResponse(code: 200) } - return TestDispatcherResponse(code: 500) + return TestDispatcherResponse(code: 200) } } @@ -223,8 +224,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { splitConfig.trafficType = trafficType splitConfig.streamingEnabled = false splitConfig.logLevel = .verbose - splitConfig.serviceEndpoints = ServiceEndpoints.builder() - .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + splitConfig.serviceEndpoints = ServiceEndpoints.builder().set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() let key: Key = Key(matchingKey: kMatchingKey, bucketingKey: nil) let builder = DefaultSplitFactoryBuilder() @@ -234,28 +234,19 @@ class SplitSdkUpdatePollingTest: XCTestCase { let client = factory!.client - var sdkReadyFired = false - var sdkUpdatedFired = false - - client.on(event: SplitEvent.sdkReady) { - sdkReadyFired = true + client.on(event: .sdkReady) { sdkReady.fulfill() } - client.on(event: SplitEvent.sdkUpdated) { - sdkUpdatedFired = true + client.on(event: .sdkUpdated) { sdkUpdate.fulfill() } - wait(for: [sdkReady, sdkUpdate], timeout: 30) + wait(for: [sdkReady, sdkUpdate], timeout: 10) // wait for sdk update ThreadUtils.delay(seconds: 1.0) - XCTAssertTrue(sdkReadyFired) - XCTAssertTrue(sdkUpdatedFired) - - let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() diff --git a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift index cccb70ba2..9529f123b 100644 --- a/SplitTests/Integration/streaming/MySegmentUpdateTest.swift +++ b/SplitTests/Integration/streaming/MySegmentUpdateTest.swift @@ -1,4 +1,3 @@ -// // MySegmentUpdateTest.swift // SplitTests // @@ -450,9 +449,9 @@ class MySegmentUpdateTest: XCTestCase { func testSdkRestartMembershipsSyncIfNewFlag() throws { var sdkReadyFired = false - var cacheReadyFired = true let sdkReady = XCTestExpectation(description: "SDK should be ready") let cacheReadyExp = XCTestExpectation(description: "Cache should be ready") + let sdkUpdateExp = XCTestExpectation(description: "SDK Should fire updated") let segmentsHit = XCTestExpectation(description: "/memberships should be hit at least once") var membershipsHit = 0 @@ -510,7 +509,10 @@ class MySegmentUpdateTest: XCTestCase { client?.on(event: .sdkReadyFromCache) { cacheReadyExp.fulfill() - cacheReadyFired = true + } + + client?.on(event: .sdkUpdated) { + sdkUpdateExp.fulfill() } wait(for: [segmentsHit], timeout: 3) @@ -529,7 +531,7 @@ class MySegmentUpdateTest: XCTestCase { waitExp = XCTestExpectation(description: "Just waiting") waitExp.isInverted = true // Inverted expectation - wait(for: [waitExp], timeout: 5) + wait(for: [waitExp, sdkUpdateExp], timeout: 5) XCTAssertGreaterThan(membershipsHit, 2, "If new flags with segments arrive, the mechanism should be restarted and SDK should hit /memberships many times again") // Cleanup diff --git a/SplitTests/Integration/streaming/StreamingAuthFail4xxTest.swift b/SplitTests/Integration/streaming/StreamingAuthFail4xxTest.swift index 0cd46bee5..545a6361a 100644 --- a/SplitTests/Integration/streaming/StreamingAuthFail4xxTest.swift +++ b/SplitTests/Integration/streaming/StreamingAuthFail4xxTest.swift @@ -53,24 +53,26 @@ class StreamingAuthFail4xxTest: XCTestCase { var timeOutFired = false var sdkReadyFired = false - client.on(event: SplitEvent.sdkReady) { + client.on(event: .sdkReady) { sdkReadyFired = true sdkReadyExpectation.fulfill() } - client.on(event: SplitEvent.sdkReadyTimedOut) { + client.on(event: .sdkReadyTimedOut) { timeOutFired = true sdkReadyExpectation.fulfill() } wait(for: [sdkReadyExpectation, mySegExp, splitsChgExp], timeout: 20) + + ThreadUtils.delay(seconds: 10) XCTAssertTrue(sdkReadyFired) XCTAssertFalse(timeOutFired) XCTAssertEqual(1, sseAuthHits) XCTAssertEqual(0, sseConnHits) - XCTAssertEqual(2, mySegmentsHits) - XCTAssertEqual(2, splitsChangesHits) + XCTAssertEqual(1, mySegmentsHits) // For Smart Pausing + XCTAssertGreaterThan(splitsChangesHits, 5) let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { @@ -90,9 +92,7 @@ class StreamingAuthFail4xxTest: XCTestCase { } if request.isMySegmentsEndpoint() { self.mySegmentsHits+=1 - if self.mySegmentsHits > 1 { - self.mySegExp.fulfill() - } + self.mySegExp.fulfill() return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) } if request.isAuthEndpoint() { @@ -110,6 +110,5 @@ class StreamingAuthFail4xxTest: XCTestCase { return self.streamingBinding! } } - } diff --git a/SplitTests/Integration/streaming/TelemetryIntegrationTest.swift b/SplitTests/Integration/streaming/TelemetryIntegrationTest.swift index 29900b878..3f835d3e5 100644 --- a/SplitTests/Integration/streaming/TelemetryIntegrationTest.swift +++ b/SplitTests/Integration/streaming/TelemetryIntegrationTest.swift @@ -112,7 +112,6 @@ class TelemetryIntegrationTest: XCTestCase { splitConfig.telemetryRefreshRate = 99999 splitConfig.impressionRefreshRate = 99999 splitConfig.eventsPushRate = 99999 - //splitConfig.isDebugModeEnabled = true let key: Key = Key(matchingKey: userKey) let builder = DefaultSplitFactoryBuilder() @@ -127,11 +126,11 @@ class TelemetryIntegrationTest: XCTestCase { let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") - client.on(event: SplitEvent.sdkReady) { + client.on(event: .sdkReady) { sdkReadyExpectation.fulfill() } - client.on(event: SplitEvent.sdkReadyTimedOut) { + client.on(event: .sdkReadyTimedOut) { IntegrationHelper.tlog("TIMEOUT") } diff --git a/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift b/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift index b3e57df84..26967025c 100644 --- a/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift +++ b/SplitTests/Service/Splits/SplitsBgSyncWorkerTest.swift @@ -19,7 +19,7 @@ class SplitsBgSyncWorkerTest: XCTestCase { var splitChangeProcessor: SplitChangeProcessorStub! var ruleBasedSegmentChangeProcessor: RuleBasedSegmentChangeProcessorStub! var splitsSyncWorker: BackgroundSyncWorker! - var generalInfoStorage: GeneralInfoStorageMock! + var generalInfoStorage = GeneralInfoStorageMock() override func setUp() { splitFetcher = HttpSplitFetcherStub() From c9c139964a675a88a0c881dbde5f3cfd7dd9a306 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 17:02:48 -0300 Subject: [PATCH 15/17] Update Split/Network/Sync/FeatureFlagsSynchronizer.swift Co-authored-by: gthea --- Split/Network/Sync/FeatureFlagsSynchronizer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 5bf27a5de..cdfbbb63a 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -90,7 +90,7 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { // MARK: Important. This should be called before loadLocal() // MARK: Part of /memberships hits optimization if storageContainer.generalInfoStorage.getSegmentsInUse() == nil && storageContainer.splitsStorage.changeNumber > -1 { - Logger.v("Force Parsing DB") + Logger.v("Force Parsing flags") splitsStorage.forceParsing() ruleBasedSegmentsStorage.forceParsing() TimeChecker.logInterval("Time for Force Parsing", startTime: start) From 50becec85ab460a60c36155a86f6eafc415ecd77 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 17:03:57 -0300 Subject: [PATCH 16/17] Tests fixed --- Split/Events/SplitEventsManager.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 949154082..d78c70f11 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -148,7 +148,6 @@ class DefaultSplitEventsManager: SplitEventsManager { self.triggered.append(event) switch event { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: - print("*********** EVENT !! **************\n\(event)\n*********** EVENT !! **************") if isTriggered(external: .sdkReady) { trigger(event: .sdkUpdated) continue From 55a63624ca3a3bb47c9874fd8421f298f1388b47 Mon Sep 17 00:00:00 2001 From: Martin Cardozo Date: Fri, 22 Aug 2025 17:15:45 -0300 Subject: [PATCH 17/17] Passing tests --- Split/Events/SplitEventsManager.swift | 3 ++ .../Sync/SplitSdkUpdatePollingTest.swift | 29 +++---------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index d78c70f11..61bca0036 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -148,7 +148,9 @@ class DefaultSplitEventsManager: SplitEventsManager { self.triggered.append(event) switch event { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: + print(" EVENT !! **************\n\(event)\n EVENT !! **************") if isTriggered(external: .sdkReady) { + print(" SDK UPDATED ") trigger(event: .sdkUpdated) continue } @@ -197,6 +199,7 @@ class DefaultSplitEventsManager: SplitEventsManager { if !isTriggered(external: .sdkReadyFromCache) { self.trigger(event: .sdkReadyFromCache) } + print(" --------- *** TRIGGER READy") self.trigger(event: .sdkReady) } } diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index 3f5c6f32c..05997d637 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -60,24 +60,12 @@ class SplitSdkUpdatePollingTest: XCTestCase { } else if index == self.spExp.count { self.spExp[index - 1].fulfill() } - let json = IntegrationHelper.loadSplitChangeFileJson(name: "splitschanges_no_segments", sourceClass: IntegrationHelper()) + let json = IntegrationHelper.loadSplitChangeFileJson(name: "splitchanges_1", sourceClass: IntegrationHelper()) return TestDispatcherResponse(code: 200, data: Data(json!.utf8)) } if request.isMySegmentsEndpoint() { - self.mySegmentsHits+=1 - let hit = self.mySegmentsHits - var json = IntegrationHelper.emptyMySegments - if hit > 2 { - var mySegments = [String]() - for i in 1...hit { - mySegments.append("segment\(i)") - } - - json = IntegrationHelper.buildSegments(regular: mySegments) - return TestDispatcherResponse(code: 200, data: Data(json.utf8)) - } - return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.mySegments(names: ["", ""]).utf8)) + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) } if request.isAuthEndpoint() { @@ -166,7 +154,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { let sdkUpdate = XCTestExpectation(description: "SDK Update Expectation") let splitConfig: SplitClientConfig = SplitClientConfig() - splitConfig.segmentsRefreshRate = 99999 + splitConfig.segmentsRefreshRate = 2 splitConfig.featuresRefreshRate = 2 splitConfig.impressionRefreshRate = 99999 splitConfig.sdkReadyTimeOut = 60000 @@ -183,25 +171,16 @@ class SplitSdkUpdatePollingTest: XCTestCase { let client = factory!.client - var sdkReadyFired = false - var sdkUpdatedFired = false - client.on(event: SplitEvent.sdkReady) { - sdkReadyFired = true sdkReady.fulfill() } client.on(event: SplitEvent.sdkUpdated) { - sdkUpdatedFired = true sdkUpdate.fulfill() } wait(for: [sdkReady, sdkUpdate], timeout: 30) - XCTAssertTrue(sdkReadyFired) - XCTAssertTrue(sdkUpdatedFired) - - let semaphore = DispatchSemaphore(value: 0) client.destroy(completion: { _ = semaphore.signal() @@ -218,7 +197,7 @@ class SplitSdkUpdatePollingTest: XCTestCase { let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.segmentsRefreshRate = 2 - splitConfig.featuresRefreshRate = 999999 + splitConfig.featuresRefreshRate = 2 splitConfig.impressionRefreshRate = 999999 splitConfig.sdkReadyTimeOut = 60000 splitConfig.trafficType = trafficType