How do I create a deadlock in Grand Central Dispatch?
Asked Answered
S

9

27

In Apple docs, it says:

Important: You should never call the dispatch_sync or dispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues.

How do you write the code to do exactly this?

Stuckey answered 13/3, 2013 at 9:21 Comment(3)
You want example of code that creates deadlock?Squawk
Yes please, for learningStuckey
See also this question for a realistic example that can easily deadlock.Garver
U
49

An intentional deadlock on a certain queue:

dispatch_queue_t queue = dispatch_queue_create("my.label", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    });

    // this will never be reached
}); 

It's clear here that the outer and inner blocks are operating on the same queue. Most cases where this will occur is in places where it's less obvious what queue the caller of the dispatch_sync is operating on. This usually occurs in a (deeply) nested stack where you're executing code in some class that was originally launched on a certain queue, and by accident you call a dispatch_sync to the same queue.

Untune answered 13/3, 2013 at 9:35 Comment(6)
When in deadlock... shouldn't the UI be unresponsive?Stuckey
@user1251004 Only if the main queue is being blocked.Nickey
In this example only the created queue is blocked. The main queue happily continues to run.Untune
Are there any other possible ways to create a deadlock in iOS?Matriculation
What happens exactly if I use a concurrent queue? Thanks a lot @JorisKluiversGuadalajara
@Guadalajara it will run normally if you use a concurrent queue in this case.Record
S
19

Simple code that creates deadlock:

dispatch_queue_t q = dispatch_queue_create("deadlock queue", DISPATCH_QUEUE_SERIAL);

NSLog(@"1");
dispatch_async(q, ^{
    NSLog(@"2");
    dispatch_sync(q, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

Log output:

1
5
2

Here internal block is scheduled to be run on serial queue q but it cannot run until current block is finished, while current block, in turn, waits internal to finish as we called it synchronously.

Squawk answered 13/3, 2013 at 9:35 Comment(0)
G
10

Interviewers often ask: "What is the simplest way to cause a deadlock?"

Obj-C:

dispatch_sync(dispatch_get_main_queue(), ^{});

Swift:

DispatchQueue.main.sync {}

Calling sync from the main thread will cause a deadlock because the main queue is a serial queue and sync stops current queue execution until passed block/closure has finished.

Grope answered 11/3, 2018 at 18:46 Comment(0)
B
9

The simplest way to block is to dispatch_sync on the current queue:

dispatch_sync(dispatch_get_current_queue(), ^{});

This blocks when the current queue is a serial queue, for example the main queue.

Bucksaw answered 13/3, 2013 at 9:44 Comment(0)
G
9

If anyone is curious, a concurrent queue does NOT deadlock if sync is called targeting the same queue. I know it's obvious but I needed to confirm only serial queues behave that way 😅

Works:

let q = DispatchQueue(label: "myQueue", attributes: .concurrent)

q.async {
    print("work async start")
    q.sync {
        print("work sync in async")
    }
    print("work async end")
}

q.sync {
    print("work sync")
}

print("done")

Fails:

Initialize q as let q = DispatchQueue(label: "myQueue") // implicitly serial queue

Goddamned answered 25/1, 2019 at 12:29 Comment(2)
Yes, because the concurent queue creates Threads for each task you add using the .sync{...}, .async{...} methods, or reuse the current threads to add the new task - this is something that the system does. So when you add Task1 using async the system creates Thread1, and Thread2 for the sync op. By calling sync inside async you are locking Thread1 until the op ends. For a deadlock test you can try to make it 3 lvls deep with async->sync->sync.Regiment
@LaurStefan you mean even with concurrent queues it will internally deadlock on the internal thread the concurrent queue spawned?Condonation
F
8

In latest Swift syntax:

let queue = DispatchQueue(label: "label")
queue.async {
    queue.sync {
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    }
    // this will never be reached
}
Fireworm answered 18/12, 2017 at 8:22 Comment(4)
Why Is the inner block waiting to start until the outer block finishes?Pye
@Pye because its a serial queueOrjonikidze
ok. got it. The async block is added to the queue, and as it is attempted to be executed, it will hit the sync function which will not start until the async block is finished executing which will never happen because the block needs to reach the end of its execution which will never happen because the queue.sync is waiting on the other.Pye
this gives me EXC_BAD_INSTRUCTION at line number 3Blowzed
E
3

In Swift 4.2 you can cause a deadlock using the following piece of code:

let serialQueue = DispatchQueue(label: "my.label")

serialQueue.sync {
    // The code inside this closure will be executed synchronously.
    serialQueue.sync {
        // The code inside this closure should also be executed synchronously and on the same queue that is still executing the outer closure ==> It will keep waiting for it to finish ==> it will never be executed ==> Deadlock.
    }
}
Egidio answered 15/11, 2018 at 14:36 Comment(0)
L
1

In my case dispatch queue calls exception in Xcode or crash on device, but usage of sleep() was more suitable for my testing purpose (it's only a freeze in the current queue).

sleep(UInt32.max)
Luhey answered 11/12, 2020 at 9:6 Comment(0)
A
0
func deadLock() {
    let serialQueue = DispatchQueue(label: "test.Deadlock", attributes: .concurrent)
    let semaphore1 = DispatchSemaphore(value: 1)
    let semaphore2 = DispatchSemaphore(value: 1)
    
    serialQueue.async {
        print("First queue")
        semaphore1.wait()
        sleep(2)
        semaphore2.wait()
        sleep(2)
        semaphore2.signal()
        semaphore1.signal()
        print("first complete")
    }
    
    serialQueue.async {
        print("second queue")
        semaphore2.wait()
        sleep(2)
        semaphore1.wait()
        sleep(2)
        semaphore1.signal()
        semaphore2.signal()
        print("second complete")
        
    }
}
Alie answered 9/7, 2022 at 5:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.