Skip to content

[Infra] Add FIRAllocatedUnfairLock type #14825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
14 changes: 8 additions & 6 deletions FirebaseCore/Internal/Tests/Unit/HeartbeatStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand All @@ -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.")
}
}
}
}
Expand Down
Loading