Details
-
Type:
Bug
-
Status: Open
-
Priority:
Medium
-
Resolution: Unresolved
-
Component/s: None
-
Labels:None
-
Environment:
MacBook Pro late 2013.
macOS Catalina 10.15.6
Swift 5.3
-
Radar URL:
Description
I found this really strange bug in which the print function causes a deadlock when called within a dispatch queue.
The deadlock occurs at the line
print("AuthorizationManager.description: BEFORE queue")
in AuthorizationManager.description, which is indirectly called by
print(self)
Intriguingly, changing the above line to any of the following prevents the deadlock:
print(self.description) print("\(self)") print(self, to: &self.someString)
Furthermore, removing the line
print("Refreshing")
also prevents the deadlock.
I suspect that this bug is related to the fact that the print function is synchronized.
I also tested out other locking mechanisms instead of DispatchQueue, including NSLock, NSRecursiveLock and even this fancy implementation from swift-log, and I experienced the exact same bug.
I posted this issue on the swift forums, and a commenter summarized the cause of the bug as follows:
So I found that for a dead lock to happen, you need to repeatedly and concurrently do the following:
- You run a block in dispatchQueue,
- The block prints anything [In this case it is the line `print("Refreshing")`],
- The block runs print(X) on a separated concurrent queue, where
- X is CustomStringConvertible that uses dispatchQueue inside description.
Here is the code that causes the issue:
import Foundation class AuthorizationManager: CustomStringConvertible { let dispatchQueue = DispatchQueue(label: "AuthorizationManager") var description: String { print("AuthorizationManager.description: BEFORE queue") // MARK: - Deadlock Occurs Here - return dispatchQueue.sync { print("AuthorizationManager.description: INSIDE queue") return "I am an authorization manager" } } func refreshTokens() { dispatchQueue.sync { print("Refreshing") } DispatchQueue.global().async { print(self) } } } /// Entry Point. func testDeadlock2() { let authorizationManager = AuthorizationManager() for i in 0..<100 { print(i) authorizationManager.refreshTokens() } print("\n--- Finished ---\n") }