Wednesday, February 15, 2017

Grand Central Dispatch’s Achilles Heel

Wil Shipley (tweet):

I don’t know much about the internals of GCD so I can’t speak with authority, but it seems like this could be solved with a couple of minor changes to sync(): figure out if the destination queue is the current queue, and if so just execute the submitted block immediately and return. This wouldn’t even be a source or binary-breaking change, because, again, the current behavior is HANG the app.

And, in fact, this is the workaround third-party programmers have made for the last several years. If you do a Google search for dispatch_get_current_queue [now deprecated] you’ll see a bunch of developers complaining about that call disappearing because they were using it for this hack.


Hopefully you’re as horrified by this mess as I am. This is the very model of spaghetti code. Last week I ported this file from macOS 10.8 to 10.12 and honestly I still couldn’t come up with a good way to re-architect it. I’m bending over backwards to interact with the main thread in multiple places in this codebase and I’m not sure if I’m on the main thread or not and it’s a nightmare.

Greg Parker:

dispatch_get_current_queue() == someQueue is insufficient to avoid the deadlocks you describe.

Rob Napier:

Interestingly, they did fix this in CoreData on top of GCD. performAndWait is reentrant. But unsure how they implemented.

2 Comments RSS · Twitter

The problem is not with GCD, but with trying to use a synchronous API using asynchronous primitives.

Each time I see a call to sync(), I think something is fishy. You should almost never have to perform a sync call on an other thread.

The trick to what Core Data does is to use dispatch_queue_set_specific and dispatch_get_specific. The nice thing about these is that they will not only check the current queue, but also ancestors, something like a responder chain approach. This can be quite easily used to avoid deadlocks.

I do agree using sync at all is a bit of a code smell, but some code ends up becoming very unwieldy if you have to make it all async (eg imagine Core Data with only async access).

Leave a Comment