Fix crash on iOS 17
This fixes an arithmetic overflow crash on iOS 17 caused by the fallback lock. In iOS 17, Swift Mutexes are not available, so we have a fallback class for NdbUseLock that does not make use of them. This allows some thread safety for iOS 17 users, but unfortunately not as much as iOS 18+ users. This attempts to fix those remaining race conditions and subsequent crashes by using `NSLock` in the fallback class, which is available on iOS 17. Closes: https://github.com/damus-io/damus/issues/3512 Changelog-Fixed: Fixed a crash on iOS 17 that would happen on startup Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import Synchronization
|
import Synchronization
|
||||||
|
import Foundation
|
||||||
|
|
||||||
extension Ndb {
|
extension Ndb {
|
||||||
/// Creates a `sync` mechanism for coordinating usages of ndb (read or write) with the app's ability to close ndb.
|
/// Creates a `sync` mechanism for coordinating usages of ndb (read or write) with the app's ability to close ndb.
|
||||||
@@ -94,6 +95,11 @@ extension Ndb {
|
|||||||
class FallbackUseLock: UseLockProtocol {
|
class FallbackUseLock: UseLockProtocol {
|
||||||
/// Number of functions using the `ndb` object (for reading or writing data)
|
/// Number of functions using the `ndb` object (for reading or writing data)
|
||||||
private var ndbUserCount: UInt = 0
|
private var ndbUserCount: UInt = 0
|
||||||
|
/// Lock for protecting access to `ndbUserCount`
|
||||||
|
private let ndbUserCountLock = NSLock()
|
||||||
|
/// Lock for protecting access to `ndbIsOpen`
|
||||||
|
private let ndbIsOpenLock = NSLock()
|
||||||
|
private var ndbIsOpen: Bool = false
|
||||||
/// Semaphore for general access to `ndb`. A closing task requires exclusive access. Users of `ndb` (read/write tasks) share the access
|
/// Semaphore for general access to `ndb`. A closing task requires exclusive access. Users of `ndb` (read/write tasks) share the access
|
||||||
private let ndbAccessSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
private let ndbAccessSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
||||||
/// How long a thread can block before throwing an error
|
/// How long a thread can block before throwing an error
|
||||||
@@ -120,18 +126,29 @@ extension Ndb {
|
|||||||
/// Implementation note: NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
/// Implementation note: NEVER change this to `async`! This is a blocking operation, so we want to minimize the time of the operation
|
||||||
func waitUntilNdbCanClose(thenClose operation: () -> Bool, maxTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws {
|
func waitUntilNdbCanClose(thenClose operation: () -> Bool, maxTimeout: DispatchTimeInterval = DEFAULT_TIMEOUT) throws {
|
||||||
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
let ndbIsOpen = operation()
|
ndbIsOpenLock.lock()
|
||||||
|
ndbIsOpen = operation()
|
||||||
if ndbIsOpen {
|
if ndbIsOpen {
|
||||||
ndbAccessSemaphore.signal()
|
ndbAccessSemaphore.signal()
|
||||||
}
|
}
|
||||||
|
ndbIsOpenLock.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks `ndb` as open to allow other users to use it. Do not call this more than once
|
/// Marks `ndb` as open to allow other users to use it. Do not call this more than once
|
||||||
func markNdbOpen() {
|
func markNdbOpen() {
|
||||||
|
ndbIsOpenLock.lock()
|
||||||
|
if !ndbIsOpen {
|
||||||
|
ndbIsOpen = true
|
||||||
ndbAccessSemaphore.signal()
|
ndbAccessSemaphore.signal()
|
||||||
}
|
}
|
||||||
|
ndbIsOpenLock.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
private func incrementUserCount(maxTimeout: DispatchTimeInterval = .seconds(2)) throws {
|
private func incrementUserCount(maxTimeout: DispatchTimeInterval = .seconds(2)) throws {
|
||||||
|
ndbUserCountLock.lock()
|
||||||
|
defer { ndbUserCountLock.unlock() }
|
||||||
|
|
||||||
|
// Signal that ndb cannot close while we have at least one user using ndb
|
||||||
if ndbUserCount == 0 {
|
if ndbUserCount == 0 {
|
||||||
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
try ndbAccessSemaphore.waitOrThrow(timeout: .now() + maxTimeout)
|
||||||
}
|
}
|
||||||
@@ -139,6 +156,9 @@ extension Ndb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func decrementUserCount() {
|
private func decrementUserCount() {
|
||||||
|
ndbUserCountLock.lock()
|
||||||
|
defer { ndbUserCountLock.unlock() }
|
||||||
|
|
||||||
ndbUserCount -= 1
|
ndbUserCount -= 1
|
||||||
// Signal that ndb can close if we have zero users using ndb
|
// Signal that ndb can close if we have zero users using ndb
|
||||||
if ndbUserCount == 0 {
|
if ndbUserCount == 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user