From f949980d4acdd90395c207c5ce25b7be9f8bf9cf Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Fri, 10 Jan 2025 15:47:14 -0600 Subject: [PATCH 1/3] fix: improving performance of FlagSynchronizer creation --- LaunchDarkly/LaunchDarkly/LDClient.swift | 6 ++--- .../Cache/FeatureFlagCache.swift | 22 +++++++++++++++++++ .../Cache/FeatureFlagCacheSpec.swift | 18 +++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/LaunchDarkly/LaunchDarkly/LDClient.swift b/LaunchDarkly/LaunchDarkly/LDClient.swift index 9d905db3..afa93fc1 100644 --- a/LaunchDarkly/LaunchDarkly/LDClient.swift +++ b/LaunchDarkly/LaunchDarkly/LDClient.swift @@ -207,9 +207,7 @@ public class LDClient { os_log("%s runMode aborted. Old runMode equals new runMode", log: config.logger, type: .debug, typeName(and: #function)) return } - - let cachedData = self.flagCache.getCachedData(cacheKey: self.context.fullyQualifiedHashedKey(), contextHash: self.context.contextHash()) - + let lastUpdated = self.flagCache.getCachedDataLastUpdatedDate(cacheKey: self.context.fullyQualifiedHashedKey(), contextHash: self.context.contextHash()) let willSetSynchronizerOnline = isOnline && isInSupportedRunMode flagSynchronizer.isOnline = false let streamingModeVar = ConnectionInformation.effectiveStreamingMode(config: config, ldClient: self) @@ -217,7 +215,7 @@ public class LDClient { flagSynchronizer = serviceFactory.makeFlagSynchronizer(streamingMode: streamingModeVar, pollingInterval: config.flagPollingInterval(runMode: runMode), useReport: config.useReport, - lastUpdated: cachedData.lastUpdated, + lastUpdated: lastUpdated, service: service, onSyncComplete: onFlagSyncComplete) flagSynchronizer.isOnline = willSetSynchronizerOnline diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift index 43265f11..a916331d 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift @@ -32,6 +32,15 @@ protocol FeatureFlagCaching { /// /// func getCachedData(cacheKey: String, contextHash: String) -> (items: StoredItems?, etag: String?, lastUpdated: Date?) + + /// Retrieve the date the cache for the given key was last updated. See getCachedData for more information. + /// + /// - parameter cacheKey: The index key into the local cache store. + /// - parameter contextHash: A hash value representing a fully unique context. + /// + /// - returns: The date the cache was last considered up-to-date. If there are no cached + /// values, this should return nil. + func getCachedDataLastUpdatedDate(cacheKey: String, contextHash: String) -> Date? // When we update the cache, we save the flag data and if we have it, an // etag. For polling, we should always have the flag data and an etag @@ -99,6 +108,19 @@ final class FeatureFlagCache: FeatureFlagCaching { return (items: cachedFlags.flags, etag: etag, lastUpdated: Date(timeIntervalSince1970: TimeInterval(lastUpdated / 1_000))) } + + func getCachedDataLastUpdatedDate(cacheKey: String, contextHash: String) -> Date? { + + var cachedContexts: [String: Int64] = [:] + if let cacheMetadata = keyedValueCache.data(forKey: "cached-contexts") { + cachedContexts = (try? JSONDecoder().decode([String: Int64].self, from: cacheMetadata)) ?? [:] + } + + guard let lastUpdated = cachedContexts[cacheKey] + else { return nil } + + return Date(timeIntervalSince1970: TimeInterval(lastUpdated / 1_000)) + } func saveCachedData(_ storedItems: StoredItems, cacheKey: String, contextHash: String, lastUpdated: Date, etag: String?) { diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift index d76417af..55fd769e 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift @@ -178,4 +178,22 @@ final class FeatureFlagCacheSpec: XCTestCase { let setMetadata = try JSONDecoder().decode([String: Int64].self, from: mockValueCache.setReceivedArguments!.value) XCTAssertEqual(setMetadata, [hashedContextKey: now.millisSince1970]) } + + func testGetCachedDataLastUpdatedDate() { + let now = Date() + let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5) + flagCache.saveCachedData(testFlagCollection.flags, cacheKey: "key", contextHash: "hash", lastUpdated: now, etag: "example-etag") + + let lastUpdated = flagCache.getCachedDataLastUpdatedDate(cacheKey: "key", contextHash: "hash") + XCTAssertEqual(lastUpdated!.millisSince1970, now.millisSince1970, accuracy: 1_000) + } + + func testGetCachedDataLastUpdatedDateKeyDoesntExist() { + let now = Date() + let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5) + flagCache.saveCachedData(testFlagCollection.flags, cacheKey: "key", contextHash: "hash", lastUpdated: now, etag: "example-etag") + + let lastUpdated = flagCache.getCachedDataLastUpdatedDate(cacheKey: "bogus", contextHash: "bogusHash") + XCTAssertEqual(lastUpdated, nil) + } } From 6af4e537cf1e22231d9e50ef35de6e0c31b06f71 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 14 Jan 2025 13:51:11 -0600 Subject: [PATCH 2/3] fixing lint issues --- .../LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift | 4 ++-- .../ServiceObjects/Cache/FeatureFlagCacheSpec.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift index a916331d..e86fe2e8 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/Cache/FeatureFlagCache.swift @@ -32,7 +32,7 @@ protocol FeatureFlagCaching { /// /// func getCachedData(cacheKey: String, contextHash: String) -> (items: StoredItems?, etag: String?, lastUpdated: Date?) - + /// Retrieve the date the cache for the given key was last updated. See getCachedData for more information. /// /// - parameter cacheKey: The index key into the local cache store. @@ -108,7 +108,7 @@ final class FeatureFlagCache: FeatureFlagCaching { return (items: cachedFlags.flags, etag: etag, lastUpdated: Date(timeIntervalSince1970: TimeInterval(lastUpdated / 1_000))) } - + func getCachedDataLastUpdatedDate(cacheKey: String, contextHash: String) -> Date? { var cachedContexts: [String: Int64] = [:] diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift index 55fd769e..4f5d2794 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/Cache/FeatureFlagCacheSpec.swift @@ -178,7 +178,7 @@ final class FeatureFlagCacheSpec: XCTestCase { let setMetadata = try JSONDecoder().decode([String: Int64].self, from: mockValueCache.setReceivedArguments!.value) XCTAssertEqual(setMetadata, [hashedContextKey: now.millisSince1970]) } - + func testGetCachedDataLastUpdatedDate() { let now = Date() let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5) @@ -187,7 +187,7 @@ final class FeatureFlagCacheSpec: XCTestCase { let lastUpdated = flagCache.getCachedDataLastUpdatedDate(cacheKey: "key", contextHash: "hash") XCTAssertEqual(lastUpdated!.millisSince1970, now.millisSince1970, accuracy: 1_000) } - + func testGetCachedDataLastUpdatedDateKeyDoesntExist() { let now = Date() let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5) From fa307c2e059a7ef009e85e357fae8e38055be5e9 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Tue, 14 Jan 2025 14:32:34 -0600 Subject: [PATCH 3/3] fixing tests --- LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift index 9268eab8..7590ae2e 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift @@ -382,7 +382,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.serviceFactoryMock.makeEventReporterReceivedService?.context) == testContext.context } it("uncaches the new contexts flags") { - expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 2 + expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 1 expect(testContext.featureFlagCachingMock.getCachedDataReceivedArguments?.cacheKey) == testContext.context.fullyQualifiedHashedKey() } it("records an identify event") { @@ -421,7 +421,7 @@ final class LDClientSpec: QuickSpec { expect(testContext.serviceFactoryMock.makeEventReporterReceivedService?.context) == testContext.context } it("uncaches the new contexts flags") { - expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 2 + expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 1 expect(testContext.featureFlagCachingMock.getCachedDataReceivedArguments?.cacheKey) == testContext.context.fullyQualifiedHashedKey() } it("records an identify event") {