IOS implements an example of deadlock causing UI feigned animation

  • 2021-09-16 08:16:41
  • OfStack

IOS Realizes an Example of UI Fake Death Caused by Deadlock

Phenomenon

When APP is started for a period of time (about half an hour), it is often found that App interface "freezes to death". Simultaneous background output:


[CocoaGoPush]WorkThreadProc end

At this time, App is in a "suspended animation" state, and there is no response when clicking anywhere on the screen. iPhone has no response except opening and closing the screen (including pressing Home key), and of course it cannot be unlocked (but it can be restarted). If the application is terminated with Xcode, iPhone returns to normal.

Note: App uses the CocoaGoPush framework.

Discover

Originally, it was thought that there was an infinite loop in the main thread of the program, which led to UI being unresponsive. But when I clicked the Pause button in the Debug toolbar to list the currently running threads, I found that this was not the case, but was used for deadlocks. After debugging pauses, the breakpoint stops at this sentence:


app.gopushLock.lock()// MARK: yhy removed  This line causes a deadlock on the main thread 

app. gopushLock is an NSRecursiveLock object:


let gopushLock = NSRecursiveLock()

NSRecursiveLock is a recursive lock that does not cause a deadlock when one lock is requested multiple times by the same thread. However, if programmers use recursive locks in two threads by mistake, it is easy to cause "deadlock": two threads lock the same lock at the same time, find that the lock is locked at the same time, and wait for each other to unlock, resulting in both threads being unable to execute. Especially when one party is the main thread, the main thread is blocked and UI is suspended animation. In this example, we also find that the thread where gopush is located also stops, and no longer listens for gopush messages and maintains a heartbeat.

Examining the code found that the code used this recursive lock in another place:


NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler:{
      (response, data, error) -> Void in

      if (error != nil) {
        app.gopushLock.lock()
        app.isGoPushFetchingMessage = false
        app.gopushLock.unlock()
        println("-----------GoPush Message Guard fail to fetch offline message. err = \(error.localizedDescription)-----------")
        ...
 })

The NSURLConection. sendAysnchronousRequest method causes the request to be sent in a new thread, so app. gopushLock. lock () is actually called in a child thread. The other one (the first code) is called in the main thread, which leads to "race".

Solve

Method 1

Comment the recursive lock call in the main thread, leaving only the recursive lock call in the child thread.

Method 2

Use different locks in the main thread, such as redefining an NSLock specifically for the main thread, and distinguish it from gopushLock in the child thread.

Method 3

Change the type of gopushLock from NSRecursiveLock to NSLock. As the name implies, a recursive lock is designed for code that needs to be synchronized in a loop or recursion, but it does not prevent two threads from accessing the code in the lock at the same time. On the contrary, NSLock can prevent two threads from accessing the code in the lock at the same time, but it cannot avoid nesting locks in the synchronous code in the same thread. Examining the call to recursive locks in paragraph 2, we find that there is no need to use recursive locks here, because there is neither recursion nor loop in the code. So you can safely change gopushLock to NSLock instead of NSRecursiveLock.

Thank you for reading, hope to help everyone, thank you for your support to this site!


Related articles: