Posts

Post not yet marked as solved
1 Replies
241 Views
Hello, I recently implemented a lock that uses OSAllocatedUnfairLock on iOS 16+ and os_unfair_lock on below iOS 16. I know that using os_unfair_lock with Swift is tricky, and it is error-prone. For example, the famous open-source library Alamofire has even been using os_unfair_lock incorrectly in terms of Swift's lifecycle view. (They fixed it as in the link [1] ) So, I implemented a lock like below. To use os_unfair_lock safely, I used the Noncopyable protocol which was added in Swift 5.9 [2]. Also, I allocated memory on the heap to use os_unfair_lock. public struct UnfairLock: ~Copyable { public init() { if #available(iOS 16.0, *) { _osAllocatedUnfairLock = OSAllocatedUnfairLock() } else { self.unfairLock = UnsafeMutablePointer.allocate(capacity: 1) } } deinit { if #unavailable(iOS 16.0) { unfairLock!.deallocate() } } public func lock() { if #available(iOS 16.0, *) { osAllocatedUnfairLock.lock() } else { os_unfair_lock_lock(unfairLock!) } } public func unlock() { if #available(iOS 16.0, *) { osAllocatedUnfairLock.unlock() } else { os_unfair_lock_unlock(unfairLock!) } } public func with<T>(_ closure: () -> T) -> T { lock() defer { unlock() } return closure() } private var _osAllocatedUnfairLock: Any? private var unfairLock: UnsafeMutablePointer<os_unfair_lock_s>? @available(iOS 16.0, *) private var osAllocatedUnfairLock: OSAllocatedUnfairLock<Void> { // swiftlint:disable force_cast _osAllocatedUnfairLock as! OSAllocatedUnfairLock // swiftlint:enable force_cast } } However, I got several crashes on iOS 14-15 users like this (This app targets iOS 14+ and on iOS 16+, it uses OSAllocatedUnfairLock): (Sorry for using a third-party crash reporting tool's log, but I think it is enough to understand the issue) BUG IN CLIENT OF LIBPLATFORM: os_unfair_lock is corrupt Crashed: com.foo.bar.queue 0 libsystem_platform.dylib 0x6144 _os_unfair_lock_corruption_abort + 88 1 libsystem_platform.dylib 0xa20 _os_unfair_lock_lock_slow + 320 2 FoooBarr 0x159416c closure #1 in static FooBar.baz() + 6321360 3 FoooBarr 0x2e65b8 thunk for @escaping @callee_guaranteed @Sendable () -> () + 4298794424 (<compiler-generated>:4298794424) 4 libdispatch.dylib 0x1c04 _dispatch_call_block_and_release + 32 5 libdispatch.dylib 0x3950 _dispatch_client_callout + 20 6 libdispatch.dylib 0x6e04 _dispatch_continuation_pop + 504 7 libdispatch.dylib 0x6460 _dispatch_async_redirect_invoke + 596 8 libdispatch.dylib 0x14f48 _dispatch_root_queue_drain + 388 9 libdispatch.dylib 0x15768 _dispatch_worker_thread2 + 164 10 libsystem_pthread.dylib 0x1174 _pthread_wqthread + 228 11 libsystem_pthread.dylib 0xf50 start_wqthread + 8 ( libplatform's source code [3] suggests that __ulock_wait returns error, but I don't know the details) Per @eskimo 's suggestion in [4], I will change my code to use NSLock until OSAllocatedUnfairLock is available on all users' devices (i.e. iOS 16+), but I still want to know why this crash happens. I believe that making a struct Noncopyable is enough to use os_unfair_lock safely, but it seems that it is not enough. Did I miss something? Or is there any other way to use os_unfair_lock safely? [1] https://github.com/Alamofire/Alamofire/commit/1b89a57c2f272408b84d20132a2ed6628e95d3e2 [2] https://github.com/apple/swift-evolution/blob/1b0b339bc3072a83b5a6a529ae405a0f076c7d5d/proposals/0390-noncopyable-structs-and-enums.md [3] https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/libplatform/src/os/lock.c#L555 [4] https://forums.developer.apple.com/forums/thread/712379
Posted
by daniel.l.
Last updated
.