Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions FirebaseRemoteConfig/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased
- [fixed] Fixed a potential deadlock in an internal dependency that could occur
during app foregrounding when reading cached settings. (#15394)

# 12.3.0
- [fixed] Add missing GoogleUtilities dependency to fix SwiftPM builds when
building dynamically linked libraries. (#15276)
Expand Down
76 changes: 55 additions & 21 deletions FirebaseSessions/Sources/Settings/SettingsCacheClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
@preconcurrency internal import GoogleUtilities
#endif // SWIFT_PACKAGE

internal import FirebaseCoreInternal

/// CacheKey is like a "key" to a "safe". It provides necessary metadata about the current cache to
/// know if it should be expired.
struct CacheKey: Codable {
Expand Down Expand Up @@ -57,53 +59,84 @@
static let forCacheKey = "firebase-sessions-cache-key"
}

/// UserDefaults holds values in memory, making access O(1) and synchronous within the app, while
/// abstracting away async disk IO.
private let cache: GULUserDefaults = .standard()
private let userDefaults: GULUserDefaults
private let persistenceQueue =
DispatchQueue(label: "com.google.firebase.sessions.settings.persistence")

// This lock protects the in-memory properties.
private let inMemoryCacheLock: UnfairLock<CacheData>

private struct CacheData {
var content: [String: Any]
var key: CacheKey?
}

init(userDefaults: GULUserDefaults = .standard()) {
self.userDefaults = userDefaults
let content = (userDefaults.object(forKey: UserDefaultsKeys.forContent) as? [String: Any]) ??
[:]
var key: CacheKey?
if let data = userDefaults.object(forKey: UserDefaultsKeys.forCacheKey) as? Data {
do {
key = try JSONDecoder().decode(CacheKey.self, from: data)
} catch {
Logger.logError("[Settings] Decoding CacheKey failed with error: \(error)")
}
}
inMemoryCacheLock = UnfairLock(CacheData(content: content, key: key))

Check failure on line 86 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

task-isolated value of type 'SettingsCache.CacheData' passed as a strongly transferred parameter; later accesses could race

Check failure on line 86 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

task-isolated value of type 'SettingsCache.CacheData' passed as a strongly transferred parameter; later accesses could race

Check failure on line 86 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

task-isolated value of type 'SettingsCache.CacheData' passed as a strongly transferred parameter; later accesses could race
}

/// Converting to dictionary is O(1) because object conversion is O(1)
var cacheContent: [String: Any] {
get {
return (cache.object(forKey: UserDefaultsKeys.forContent) as? [String: Any]) ?? [:]
return inMemoryCacheLock.value().content
}
set {
cache.setObject(newValue, forKey: UserDefaultsKeys.forContent)
inMemoryCacheLock.withLock { $0.content = newValue }

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 95 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function
persistenceQueue.async {
self.userDefaults.setObject(newValue, forKey: UserDefaultsKeys.forContent)

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, visionOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

capture of 'newValue' with non-Sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

capture of 'newValue' with non-Sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-26, Xcode_26.0, iOS)

capture of 'newValue' with non-Sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, tvOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, macOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, watchOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, catalyst)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-15, Xcode_16.4, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a '@sendable' closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a `@Sendable` closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a `@Sendable` closure

Check warning on line 97 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

capture of 'newValue' with non-sendable type '[String : Any]' in a `@Sendable` closure
}
}
}

/// Casting to Codable from Data is O(n)
var cacheKey: CacheKey? {
get {
if let data = cache.object(forKey: UserDefaultsKeys.forCacheKey) as? Data {
return inMemoryCacheLock.value().key
}
set {
inMemoryCacheLock.withLock { $0.key = newValue }
persistenceQueue.async {
do {
return try JSONDecoder().decode(CacheKey.self, from: data)
try self.userDefaults.setObject(JSONEncoder().encode(newValue),
forKey: UserDefaultsKeys.forCacheKey)
} catch {
Logger.logError("[Settings] Decoding CacheKey failed with error: \(error)")
Logger.logError("[Settings] Encoding CacheKey failed with error: \(error)")
}
}
return nil
}
set {
do {
try cache.setObject(JSONEncoder().encode(newValue), forKey: UserDefaultsKeys.forCacheKey)
} catch {
Logger.logError("[Settings] Encoding CacheKey failed with error: \(error)")
}
}
}

/// Removes stored cache
func removeCache() {
cache.setObject(nil, forKey: UserDefaultsKeys.forContent)
cache.setObject(nil, forKey: UserDefaultsKeys.forCacheKey)
inMemoryCacheLock.withLock {
$0.content = [:]
$0.key = nil
}
persistenceQueue.async {
self.userDefaults.setObject(nil, forKey: UserDefaultsKeys.forContent)
self.userDefaults.setObject(nil, forKey: UserDefaultsKeys.forCacheKey)
}
}

func isExpired(for appInfo: ApplicationInfoProtocol, time: Date) -> Bool {
guard !cacheContent.isEmpty else {
let (content, key) = inMemoryCacheLock.withLock { ($0.content, $0.key) }

Check failure on line 133 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 133 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

Check failure on line 133 in FirebaseSessions/Sources/Settings/SettingsCacheClient.swift

View workflow job for this annotation

GitHub Actions / spm / spm (macos-14, Xcode_16.2, iOS)

'inout sending' parameter '$0' cannot be task-isolated at end of function

guard !content.isEmpty else {
removeCache()
return true
}
guard let cacheKey = cacheKey else {
guard let cacheKey = key else {
Logger.logError("[Settings] Could not load settings cache key")
removeCache()
return true
Expand All @@ -126,7 +159,8 @@
}

private func cacheDuration() -> TimeInterval {
guard let duration = cacheContent[Self.flagCacheDuration] as? Double else {
let content = inMemoryCacheLock.value().content
guard let duration = content[Self.flagCacheDuration] as? Double else {
return Self.cacheDurationSecondsDefault
}
Logger.logDebug("[Settings] Cache duration: \(duration)")
Expand Down
Loading