26

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?

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
BlackMouse
  • 4,442
  • 6
  • 38
  • 65

9 Answers9

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.

Joris Kluivers
  • 11,894
  • 2
  • 48
  • 47
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.

Vladimir
  • 170,431
  • 36
  • 387
  • 313
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.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
9

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.

ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
Bohdan Orlov
  • 274
  • 3
  • 3
8

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

Alexandru Motoc
  • 582
  • 7
  • 14
  • 1
    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. – Laur Stefan Jun 16 '22 at 04:55
  • @LaurStefan you mean even with concurrent queues it will internally deadlock on the internal thread the concurrent queue spawned? – CyberMew Jul 23 '22 at 18:37
7

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
}
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
  • Why Is the inner block waiting to start until the outer block finishes? – ScottyBlades Mar 25 '18 at 17:43
  • 1
    @ScottyBlades because its a serial queue – Johnykutty Apr 19 '18 at 09:09
  • 8
    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. – ScottyBlades Apr 19 '18 at 17:22
  • this gives me EXC_BAD_INSTRUCTION at line number 3 – Mansuu.... Aug 27 '19 at 12:52
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.
    }
}
Mo Abdul-Hameed
  • 6,030
  • 2
  • 23
  • 36
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)
pkis
  • 6,467
  • 1
  • 17
  • 17
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")
        
    }
}