Prechádzať zdrojové kódy

[Infra] Add `FIRAllocatedUnfairLock` type (#14825)

Co-authored-by: Morgan Chen <morganchen12@gmail.com>
Nick Cooke 10 mesiacov pred
rodič
commit
95c70596a6

+ 2 - 2
FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift

@@ -52,9 +52,9 @@ final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol {
   // MARK: - Instance Management
 
   /// Statically allocated cache of `HeartbeatStorage` instances keyed by string IDs.
-  private nonisolated(unsafe) static var cachedInstances: AtomicBox<
+  private static let cachedInstances: FIRAllocatedUnfairLock<
     [String: WeakContainer<HeartbeatStorage>]
-  > = AtomicBox([:])
+  > = FIRAllocatedUnfairLock(initialState: [:])
 
   /// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise,
   /// makes a new instance with the given `id`.

+ 66 - 0
FirebaseCore/Internal/Sources/Utilities/FIRAllocatedUnfairLock.swift

@@ -0,0 +1,66 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Foundation
+import os.lock
+
+/// A reference wrapper around `os_unfair_lock`. Replace this class with
+/// `OSAllocatedUnfairLock` once we support only iOS 16+. For an explanation
+/// on why this is necessary, see the docs:
+/// https://developer.apple.com/documentation/os/osallocatedunfairlock
+public final class FIRAllocatedUnfairLock<State>: @unchecked Sendable {
+  private var lockPointer: UnsafeMutablePointer<os_unfair_lock>
+  private var state: State
+
+  public init(initialState: sending State) {
+    lockPointer = UnsafeMutablePointer<os_unfair_lock>
+      .allocate(capacity: 1)
+    lockPointer.initialize(to: os_unfair_lock())
+    state = initialState
+  }
+
+  public convenience init() where State == Void {
+    self.init(initialState: ())
+  }
+
+  public func lock() {
+    os_unfair_lock_lock(lockPointer)
+  }
+
+  public func unlock() {
+    os_unfair_lock_unlock(lockPointer)
+  }
+
+  @discardableResult
+  public func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R {
+    let value: R
+    lock()
+    defer { unlock() }
+    value = try body(&state)
+    return value
+  }
+
+  @discardableResult
+  public func withLock<R>(_ body: () throws -> R) rethrows -> R {
+    let value: R
+    lock()
+    defer { unlock() }
+    value = try body()
+    return value
+  }
+
+  deinit {
+    lockPointer.deallocate()
+  }
+}

+ 8 - 6
FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift

@@ -405,13 +405,13 @@ class HeartbeatStorageTests: XCTestCase {
     // type '[WeakContainer<HeartbeatStorage>]' to a `@Sendable` closure
     // (`DispatchQueue.global().async { ... }`).
     final class WeakRefs: @unchecked Sendable {
-      private(set) var weakRefs: [WeakContainer<HeartbeatStorage>] = []
       // Lock is used to synchronize `weakRefs` during concurrent access.
-      private let weakRefsLock = NSLock()
+      private(set) var weakRefs =
+        FIRAllocatedUnfairLock<[WeakContainer<HeartbeatStorage>]>(initialState: [])
 
       func append(_ weakRef: WeakContainer<HeartbeatStorage>) {
-        weakRefsLock.withLock {
-          weakRefs.append(weakRef)
+        weakRefs.withLock {
+          $0.append(weakRef)
         }
       }
     }
@@ -436,8 +436,10 @@ class HeartbeatStorageTests: XCTestCase {
     // Then
     // The `weakRefs` array's references should all be nil; otherwise, something is being
     // unexpectedly strongly retained.
-    for weakRef in weakRefs.weakRefs {
-      XCTAssertNil(weakRef.object, "Potential memory leak detected.")
+    weakRefs.weakRefs.withLock { refs in
+      for weakRef in refs {
+        XCTAssertNil(weakRef.object, "Potential memory leak detected.")
+      }
     }
   }
 }