Monday, November 28, 2011

dispatch_sync_safe Deprecated

A while back I wrote a pseudo-category for GCD and posted it to Github. It contains several convenience functions. One of them was a method that makes synchronous calls “safe,” or so I thought. The code looked like this:

void dispatch_sync_safe(dispatch_queue_t queue, dispatch_block_t block) {
    // If we're already on the given queue, just run the block.
    if (dispatch_get_current_queue() == queue)
        block();
    // Otherwise, dispatch to the given queue.
    else
        dispatch_sync(queue, block);
}

The idea is that when doing a synchronous dispatch to a queue, I check if the program was already running on that queue. If it is, then the block is immediately executed. Otherwise, the block is run with a regular dispatch_sync. In my naïve implementation I thought this would prevent deadlock. The idea is nice but not correct. Consider the case when we dispatch_sync_safe to queue A. Next, we dispatch_sync_safe to queue B. Finally, we dispatch_sync_safe back to queue A. This is guaranteed deadlock.

As a result of this case, I immediately marked dispatch_sync_safe as deprecated in my Github repo. Anyone using this function thinking it is perfectly safe should think again. I did, however, add another function called dispatch_sync_checked. It is actually the same implementation as dispatch_sync_safe just under a different name. I recognize that this simple function has great value, but the old name was misleading. The moral of the story is, use dispatch_async (or one of my dispatch_async convenience methods) whenever possible. If you must run code synchronously, use dispatch_sync_checked and be very cautious of deadlock.

As I mentioned previously, dispatch_sync_checked still has great value. There is at least one case where you can guarantee you won’t deadlock. Consider the case when you have an object with a private serial dispatch queue. This queue is used to serialize access to some shared memory within the object. Using dispatch_sync_checked on the private queue allows you to keep your object’s methods synchronous and thread safe. This requires following two conditions in your critical code:
  1. Do not make synchronous calls to external objects (which could synchronously call the calling object again).
  2. Do not call dispatch_sync on any queue (or dispatch_sync_checked on any queue except the private queue).
Also, since these methods check for running on the private queue, they can call each other without worrying about deadlock. This gives similar behavior to a recursive lock without the overhead. If @synchronized(self) { … } just isn’t fast enough, consider switching to dispatch_sync_checked(_queue, ^{ … }).