Skip to content

Commit 9b46b05

Browse files
committed
fix sendable violations in sessions
1 parent ea7e8fa commit 9b46b05

File tree

7 files changed

+49
-36
lines changed

7 files changed

+49
-36
lines changed

FirebaseCore/Internal/Sources/Utilities/AtomicBox.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import Foundation
1616

17-
final class AtomicBox<T> {
17+
public final class AtomicBox<T>: @unchecked Sendable {
1818
private var _value: T
1919
private let lock = NSLock()
2020

FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallations.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ typedef void (^FIRInstallationsTokenHandler)(
5757
* as the ability to delete it. A Firebase Installation is unique by `FirebaseApp.name` and
5858
* `FirebaseApp.options.googleAppID` .
5959
*/
60-
NS_SWIFT_NAME(Installations)
60+
NS_SWIFT_NAME(Installations) NS_SWIFT_SENDABLE
6161
@interface FIRInstallations : NSObject
6262

6363
- (instancetype)init NS_UNAVAILABLE;

FirebaseSessions/Sources/ApplicationInfo.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ enum DevEnvironment: String {
3434
case autopush // Autopush environment
3535
}
3636

37-
protocol ApplicationInfoProtocol {
37+
protocol ApplicationInfoProtocol: Sendable {
3838
/// Google App ID / GMP App ID
3939
var appID: String { get }
4040

@@ -62,12 +62,12 @@ protocol ApplicationInfoProtocol {
6262
var osDisplayVersion: String { get }
6363
}
6464

65-
class ApplicationInfo: ApplicationInfoProtocol {
65+
final class ApplicationInfo: ApplicationInfoProtocol {
6666
let appID: String
6767

6868
private let networkInformation: NetworkInfoProtocol
6969
private let envParams: [String: String]
70-
private let infoDict: [String: Any]?
70+
nonisolated(unsafe) private let infoDict: [String: Any]?
7171

7272
init(appID: String, networkInfo: NetworkInfoProtocol = NetworkInfo(),
7373
envParams: [String: String] = ProcessInfo.processInfo.environment,

FirebaseSessions/Sources/Installations+InstallationsProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Foundation
1717

1818
@_implementationOnly import FirebaseInstallations
1919

20-
protocol InstallationsProtocol {
20+
protocol InstallationsProtocol: Sendable {
2121
var installationsWaitTimeInSecond: Int { get }
2222

2323
/// Override Installation function for testing

FirebaseSessions/Sources/NetworkInfo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ import Foundation
2525
@_implementationOnly import GoogleUtilities
2626
#endif // SWIFT_PACKAGE
2727

28-
protocol NetworkInfoProtocol {
28+
protocol NetworkInfoProtocol: Sendable {
2929
var networkType: GULNetworkType { get }
3030

3131
var mobileSubtype: String { get }
3232
}
3333

34-
class NetworkInfo: NetworkInfoProtocol {
34+
final class NetworkInfo: NetworkInfoProtocol {
3535
var networkType: GULNetworkType {
3636
return GULNetworkInfo.getNetworkType()
3737
}

FirebaseSessions/Sources/Settings/RemoteSettings.swift

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
// limitations under the License.
1515

1616
import Foundation
17+
internal import FirebaseCoreInternal
1718

1819
/// Extends ApplicationInfoProtocol to string-format a combined appDisplayVersion and
1920
/// appBuildVersion
2021
extension ApplicationInfoProtocol {
2122
var synthesizedVersion: String { return "\(appDisplayVersion) (\(appBuildVersion))" }
2223
}
2324

24-
class RemoteSettings: SettingsProvider {
25+
final class RemoteSettings: SettingsProvider, Sendable {
2526
private static let cacheDurationSecondsDefault: TimeInterval = 60 * 60
2627
private static let flagSessionsEnabled = "sessions_enabled"
2728
private static let flagSamplingRate = "sampling_rate"
@@ -30,47 +31,57 @@ class RemoteSettings: SettingsProvider {
3031
private static let flagSessionsCache = "app_quality"
3132
private let appInfo: ApplicationInfoProtocol
3233
private let downloader: SettingsDownloadClient
33-
private var cache: SettingsCacheClient
34+
private let cache: AtomicBox<SettingsCacheClient>
3435

3536
private var cacheDurationSeconds: TimeInterval {
36-
guard let duration = cache.cacheContent[RemoteSettings.flagCacheDuration] as? Double else {
37-
return RemoteSettings.cacheDurationSecondsDefault
37+
cache.withLock { cache in
38+
guard let duration = cache.cacheContent[RemoteSettings.flagCacheDuration] as? Double else {
39+
return RemoteSettings.cacheDurationSecondsDefault
40+
}
41+
return duration
3842
}
39-
return duration
4043
}
4144

4245
private var sessionsCache: [String: Any] {
43-
return cache.cacheContent[RemoteSettings.flagSessionsCache] as? [String: Any] ?? [:]
46+
cache.withLock { cache in
47+
return cache.cacheContent[RemoteSettings.flagSessionsCache] as? [String: Any] ?? [:]
48+
}
4449
}
4550

4651
init(appInfo: ApplicationInfoProtocol,
4752
downloader: SettingsDownloadClient,
4853
cache: SettingsCacheClient = SettingsCache()) {
4954
self.appInfo = appInfo
50-
self.cache = cache
55+
self.cache = AtomicBox<SettingsCacheClient>(cache)
5156
self.downloader = downloader
5257
}
5358

5459
private func fetchAndCacheSettings(currentTime: Date) {
55-
// Only fetch if cache is expired, otherwise do nothing
56-
guard isCacheExpired(time: currentTime) else {
57-
Logger.logDebug("[Settings] Cache is not expired, no fetch will be made.")
58-
return
60+
cache.withLock { cache in
61+
// Only fetch if cache is expired, otherwise do nothing
62+
guard isCacheExpired(cache, time: currentTime) else {
63+
Logger.logDebug("[Settings] Cache is not expired, no fetch will be made.")
64+
return
65+
}
5966
}
6067

61-
downloader.fetch { result in
62-
switch result {
63-
case let .success(dictionary):
64-
// Saves all newly fetched Settings to cache
65-
self.cache.cacheContent = dictionary
66-
// Saves a "cache-key" which carries TTL metadata about current cache
67-
self.cache.cacheKey = CacheKey(
68-
createdAt: currentTime,
69-
googleAppID: self.appInfo.appID,
70-
appVersion: self.appInfo.synthesizedVersion
71-
)
68+
downloader.fetch { result in
69+
70+
switch result {
71+
case let .success(dictionary):
72+
self.cache.withLock { cache in
73+
// Saves all newly fetched Settings to cache
74+
cache.cacheContent = dictionary
75+
// Saves a "cache-key" which carries TTL metadata about current cache
76+
cache.cacheKey = CacheKey(
77+
createdAt: currentTime,
78+
googleAppID: self.appInfo.appID,
79+
appVersion: self.appInfo.synthesizedVersion
80+
)
81+
}
7282
case let .failure(error):
7383
Logger.logError("[Settings] Fetching newest settings failed with error: \(error)")
84+
7485
}
7586
}
7687
}
@@ -102,10 +113,12 @@ extension RemoteSettingsConfigurations {
102113
}
103114

104115
func isSettingsStale() -> Bool {
105-
return isCacheExpired(time: Date())
116+
cache.withLock { cache in
117+
return isCacheExpired(cache, time: Date())
118+
}
106119
}
107120

108-
private func isCacheExpired(time: Date) -> Bool {
121+
private func isCacheExpired(_ cache: SettingsCacheClient, time: Date) -> Bool {
109122
guard !cache.cacheContent.isEmpty else {
110123
cache.removeCache()
111124
return true

FirebaseSessions/Sources/Settings/SettingsDownloadClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import Foundation
2121
@_implementationOnly import GoogleUtilities
2222
#endif // SWIFT_PACKAGE
2323

24-
protocol SettingsDownloadClient {
25-
func fetch(completion: @escaping (Result<[String: Any], SettingsDownloaderError>) -> Void)
24+
protocol SettingsDownloadClient: Sendable {
25+
func fetch(completion: @Sendable @escaping (Result<[String: Any], SettingsDownloaderError>) -> Void)
2626
}
2727

2828
enum SettingsDownloaderError: Error {
@@ -36,7 +36,7 @@ enum SettingsDownloaderError: Error {
3636
case InstallationIDError(String)
3737
}
3838

39-
class SettingsDownloader: SettingsDownloadClient {
39+
final class SettingsDownloader: SettingsDownloadClient {
4040
private let appInfo: ApplicationInfoProtocol
4141
private let installations: InstallationsProtocol
4242

@@ -45,7 +45,7 @@ class SettingsDownloader: SettingsDownloadClient {
4545
self.installations = installations
4646
}
4747

48-
func fetch(completion: @escaping (Result<[String: Any], SettingsDownloaderError>) -> Void) {
48+
func fetch(completion: @Sendable @escaping (Result<[String: Any], SettingsDownloaderError>) -> Void) {
4949
guard let validURL = url else {
5050
completion(.failure(.URLError("Invalid URL")))
5151
return

0 commit comments

Comments
 (0)