Skip to content

Commit 95c7059

Browse files
[Infra] Add FIRAllocatedUnfairLock type (#14825)
Co-authored-by: Morgan Chen <morganchen12@gmail.com>
1 parent 3663b1a commit 95c7059

File tree

3 files changed

+76
-8
lines changed

3 files changed

+76
-8
lines changed

FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol {
5252
// MARK: - Instance Management
5353

5454
/// Statically allocated cache of `HeartbeatStorage` instances keyed by string IDs.
55-
private nonisolated(unsafe) static var cachedInstances: AtomicBox<
55+
private static let cachedInstances: FIRAllocatedUnfairLock<
5656
[String: WeakContainer<HeartbeatStorage>]
57-
> = AtomicBox([:])
57+
> = FIRAllocatedUnfairLock(initialState: [:])
5858

5959
/// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise,
6060
/// makes a new instance with the given `id`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import os.lock
17+
18+
/// A reference wrapper around `os_unfair_lock`. Replace this class with
19+
/// `OSAllocatedUnfairLock` once we support only iOS 16+. For an explanation
20+
/// on why this is necessary, see the docs:
21+
/// https://developer.apple.com/documentation/os/osallocatedunfairlock
22+
public final class FIRAllocatedUnfairLock<State>: @unchecked Sendable {
23+
private var lockPointer: UnsafeMutablePointer<os_unfair_lock>
24+
private var state: State
25+
26+
public init(initialState: sending State) {
27+
lockPointer = UnsafeMutablePointer<os_unfair_lock>
28+
.allocate(capacity: 1)
29+
lockPointer.initialize(to: os_unfair_lock())
30+
state = initialState
31+
}
32+
33+
public convenience init() where State == Void {
34+
self.init(initialState: ())
35+
}
36+
37+
public func lock() {
38+
os_unfair_lock_lock(lockPointer)
39+
}
40+
41+
public func unlock() {
42+
os_unfair_lock_unlock(lockPointer)
43+
}
44+
45+
@discardableResult
46+
public func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R {
47+
let value: R
48+
lock()
49+
defer { unlock() }
50+
value = try body(&state)
51+
return value
52+
}
53+
54+
@discardableResult
55+
public func withLock<R>(_ body: () throws -> R) rethrows -> R {
56+
let value: R
57+
lock()
58+
defer { unlock() }
59+
value = try body()
60+
return value
61+
}
62+
63+
deinit {
64+
lockPointer.deallocate()
65+
}
66+
}

FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,13 @@ class HeartbeatStorageTests: XCTestCase {
405405
// type '[WeakContainer<HeartbeatStorage>]' to a `@Sendable` closure
406406
// (`DispatchQueue.global().async { ... }`).
407407
final class WeakRefs: @unchecked Sendable {
408-
private(set) var weakRefs: [WeakContainer<HeartbeatStorage>] = []
409408
// Lock is used to synchronize `weakRefs` during concurrent access.
410-
private let weakRefsLock = NSLock()
409+
private(set) var weakRefs =
410+
FIRAllocatedUnfairLock<[WeakContainer<HeartbeatStorage>]>(initialState: [])
411411

412412
func append(_ weakRef: WeakContainer<HeartbeatStorage>) {
413-
weakRefsLock.withLock {
414-
weakRefs.append(weakRef)
413+
weakRefs.withLock {
414+
$0.append(weakRef)
415415
}
416416
}
417417
}
@@ -436,8 +436,10 @@ class HeartbeatStorageTests: XCTestCase {
436436
// Then
437437
// The `weakRefs` array's references should all be nil; otherwise, something is being
438438
// unexpectedly strongly retained.
439-
for weakRef in weakRefs.weakRefs {
440-
XCTAssertNil(weakRef.object, "Potential memory leak detected.")
439+
weakRefs.weakRefs.withLock { refs in
440+
for weakRef in refs {
441+
XCTAssertNil(weakRef.object, "Potential memory leak detected.")
442+
}
441443
}
442444
}
443445
}

0 commit comments

Comments
 (0)