performBlockAndWait and deadlocks


#1

Doesn’t a call to context.performBlockAndWait() from main thread end up in deadlock by default if

  1. the context has mainQueueConcurrencyType thereby tying it to main queue and thus main thread
  2. the main queue already has a pending task at the time of performBlockAndWait() call, may be from a performBlock() call made just above this invocation.

By definiition, the main thread can not start executing the block if the context’s queue (main queue here) is not empty. Also it is the main thread itself which should be clearing that queue at the end of the run loop. If these too statements are always true then isn’t a call to performBlockWait() easily dead-lockable?


#2

If you want to dive into a detailed description of deadlocks and performBlockAndWait, you might want to read this thread:

http://stackoverflow.com/questions/32887919/xcode-7-0-ios9-sdk-deadlock-while-executing-fetch-request-with-performblockandw


#3

I tested it out with a sample program. Dispatched some regular blocks (not core data) into the main queue. Also called a performBlock to push an insert code block into the queue. In the end I called a performBlockAndWait for a fetch. What happened is that the performBlockAndWait did not bother about the other blocks (regular & core data) in the main queue. It just went ahead with its own execution and came out. The other blocks including the insert block got executed as usual after everything at the end of the run loop.

Note: I read in some blog that the calling thread waits for the context’s queue to be empty before directly executing a performBlockAndWait(). Hence the doubt in the first place. The author was trying to highlight the fact that the block is not scheduled in the queue and executed by the context’s thread, but directly performed by the calling thread. But he also suggested that the calling thread will wait for any pending blocks in the queue to be picked up before doing so, probably assuming that it is necessary for ensuring data consistency (say some data insertion/update blocks already scheduled in the queue).


#4

Do you have the link?


#5

Have been going through many blogs & forums in the past week, on core data and concurrency. Do not remember the particular site. Will paste it once I find the same.


#6

I understand, it happens to me too.

By the way, I thought the answer in the linked post below was well worth a read. Particularly:

By definition, performBlockAndWait breaks FIFO ordering because it jumps the queue of blocks that have already been enqueued.

In this situation:

  1. the main queue already has a pending task at the time of performBlockAndWait() call, may be from a performBlock() call made just above this invocation.

…according to the explanation at the link below, with code like this:

context.performBlock() {
    doStuff()
}

context.performBlockAndWait() {
    doSomethingElse()
}
  1. There won’t be any deadlock.

  2. The order of execution will always be:

    doSomethingElse()
    doStuff()

When performBlock() executes, the block given to performBlock() is scheduled for execution sometime in the future, then performBlock() returns. Next, performBlockAndWait() executes, and its block executes immediately, and the caller’s thread halts until the block returns. Subsequently, the block scheduled by performBlock() executes.

http://stackoverflow.com/questions/32198678/behavior-differences-between-performblock-and-performblockandwait

Below is an answer on the same subject from the same author three years prior to his other answer (there seem’s to be some dissent in the comments on whether he is correct):

http://stackoverflow.com/questions/11831946/nsmanagedobjectcontext-performblockandwait-doesnt-execute-on-background-thread


#7

Following could be the location I came across the original assumption. Please refer to the accepted answer.

http://stackoverflow.com/questions/11831946/nsmanagedobjectcontext-performblockandwait-doesnt-execute-on-background-thread

performBlockAndWait does NOT use the internal queue. It is a synchronous operation that executes in the context of the calling thread. Of course, it will wait until the current operations on the queue have been executed, and then that block will execute in the calling thread. This is documented (and reasserted in several WWDC presentations).

However in the first edit of that answer, he also mentions that (which I missed earlier)

performBlockAndWait uses GCD, but it also has its own layer (e.g., to prevent deadlocks). If you look at the GCD code (which is open source), you can see how synchronous calls work - and in general they synchronize with the queue and invoke the block on the thread that called the function - unless the queue is the main queue or a global queue. Also, in the WWDC talks, the Core Data engineers stress the point that performBlockAndWait will run in the calling thread.

In my original test, I did the following

  • Create mainContext with mainQueueConcurrency type
  • From the main thread invoke a performBlock on the mainContext // An insert record + 5 seconds sleep
  • From the main thread invoke a performBlockAndWait on the mainContext // A fetch records

As discussed earlier, they got executed like

  • performBlockAndWait code
  • performBlock code

After reading the edit in the above stack overflow answer, I modified the test as below

  • Create privateContext with privateQueueConcurrency type
  • From the main thread invoke a performBlock on the privateContext // An insert record + 5 seconds sleep
  • From the main thread invoke a performBlockAndWait on the privateContext // A fetch records

This time the execution was in the following order always

  • performBlock code // Ran by the private thread
  • performBlockAndWait code // Ran by the main thread, after 5 seconds delay

(I tried with multiple performBlock() calls with/without the sleep inside, before calling performBlockAndWait. Result was always the same. The performBlockAndWait call got executed in the end only. Also I print the thread object inside the block to see who is running it).

So it looks like, if the main thread is calling a performBlockAndWait() on a context tied to mainQueue, that block gets executed immediately, jumping any pending blocks in the main queue. But if main thread is calling performBlockAndWait() on a context with privateQueue then the main thread waits for the private thread to finish up the pending blocks in the private queue. Then again I am not sure whether it is due to some lock on the context that the private thread was able to acquire and keep till pending blocks are done with. I have no knowledge of how locking works in core data. One thing for sure is to stay away from performBlockAndWait() for the time being.


#8

Now only noticed the edit in your answer. It is probably the second link from where I got the original assumption. Thanks for the new link. I will go through the same.


#9

[quote=“kumar, post:8, topic:10905”]

I thought that might be the one.

Was the sleep() necessary? According to the first post, the performBlockAndWait() code will always execute first.

Oh boy. I can’t say I like that result. (I guess I added the “jump the queue” rule for performBlockAndWait() to my my mental model.)

  1. What happens if you submit two performBlock()'s to the private queue and immediately after that you call performBlockAndWait()? Do both blocks in the private queue execute before the performBlockAndWait() code? If so, then that follows the rule stated in the earlier post on private queues:

Of course, it will wait until the current operations on the queue have been executed, and then that block will execute in the calling thread. This is documented (and reasserted in several WWDC presentations).

  1. What happens to the order when you make a reentrant call with performBlockAndWait() with the different queue types:

    performBlockAndWait() {
    performBlock() { …}

      performBlockAndWait() {....}
    

    }

In both types of context queues, I guess I would expect the performBlockAndWait() code to execute in its entirety. In the case where the context’s queue is the private queue, presumably there’s nothing in the private queue when the outer performBlockAndWait() begins execution, and the outer performBlockAndWait() will get the lock and keep it until all the code inside its block executes, to wit: 1) The inner performBlock() will finish executing immediately (because it’s asynchronous), 2) The inner performBlockAndWait() must finish executing before the outer performBlockAndWait() can be said to finish executing, and thereafter the outer performBlockAndWait() will give up the lock, allowing the inner performBlock()'s code to execute.

Then again I am not sure whether it is due to some lock on the context that the private thread was able to acquire and keep till pending blocks are done with. I have no knowledge of how locking works in core data. One thing for sure is to stay away from performBlockAndWait() for the time being.

I have to keep reminding myself that even though performBlock() is asynchronous in that it immediately returns, its block cannot be executed concurrently with other blocks entered in the context queue by performBlock* methods. As you note, to prevent interference a block has to get a lock, which prevents other performBlock* blocks from executing.

One thing for sure is to stay away from performBlockAndWait() for the time being.

That one poster says he’s never needed it, and he sounds experienced.


#10
  • The sleep() was actually added for the second test, to see whether the performBlockAndWait() waits for the entire 5 seconds the performBlock() is going on. It was not necessary for the first test.

  • I submitted 20 performBlocks and then immediately called performBlockAndWait. Even repeated with another 20+1 set of calls appended to this. Always the performBlockAndWait got executed after the previously scheduled performBlocks have been completely run by the private thread (in the order 20 + 1 + 20 + 1).

  • Regarding your test, it ran as you suggested
    performBlockAndWait outer
    performBlockAndWait inner
    performBlock


#11

Thanks. And for the enlightening discussion.


#12

Thanks to you too. Take care.


#13

great explanation! I was running into issues with perform block and wait on multi- context and multi threaded application, your blog cleared my suspicions.


#14

Dealing with something similar also. Thanks for the info.