Unexpected behavior of dispatch_main on macOS

Hi! We are seeing a bit surprising behavior of dispatch_main on macOS where it seems to spawn a different thread instead of preserving the one it gets called from.

Managed to reproduce it in a completely empty command-line tool project in Xcode

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_main();
        return 0;
    }
}

I put a breakpoint on the line with dispatch_main and see that I am on Thread 1 and inside main function. That makes sense.

I resume execution and pause again. Looking at Thread output in Xcode, I can only see Thread 2.

Thread 1 is gone and the executable keeps on running.

So dispatch_main did what was expected (prevented the process from termination) but throws out the thread it was called from and creates a new one? Is that behavior expected or am I missing something?

Just a brain teaser at this point. But we could not make sense out of it. :)

Answered by DTS Engineer in 823033022
Written by ArthurValiev in 773371021
dispatch_main … seems to spawn a different thread

That’s right. See the explanation in this post.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer
Written by ArthurValiev in 773371021
dispatch_main … seems to spawn a different thread

That’s right. See the explanation in this post.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks! We also managed to confirm that by reading libdispatch source code in apple-oss-distributons repo.

My original interest in this topic was slightly different. Originally, I was investigating a bug in our product that was not triggered until very recently.

We had an command-line executable running as a launch agent that was preforming some setup and then calling dispatch_main to prevent the process from terminating.

The bug was caused by important bit of code that was put below dispatch_main in main.swift.

This was the setup roughly. All happening in main.swift without dispatching to any other thread.

// 1. setup environment

// 2. call dispatch_main

// 3. additional setup

The problem is that the code was reaching "3. additional setup" areas before recent changes. And it was a big WTF moment of why it suddenly stopped working. Now that I am looking at this code, it makes perfect sense. It should have never reached anything after dispatch_main.

Nothing in this original setup in main.swift was touched but it was clearly working for the last 2-3 years until recent changes in the additional setup that revealed the bug.

I will try to reproduce it outside of our product and file a feedback. But It was just mind blowing that code that was placed below dispatch_main was getting executed and now out of the sudden stopped. And that led us to the investigation of what dispatch_main actually does underneath :)

dispatch_main is marked as DISPATCH_NORETURN. It shouldn’t ever come back. If you’re able to reproduce this, please do file a bug and post your bug number here.

Note that this is not true of -[NSRunLoop run]. That is allowed to return, and it’s a source of considerable confusion when it does.

Finally, xpc_main will either call dispatch_main or -[NSRunLoop run] depending on your XPC service configuration. It’s also marked as not returning (XPC_NORETURN). If it calls the run loop and that returns, xpc_main traps.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yep. We found the explanation for this behavior just now.

This bit from my message before is incorrect. It was never reaching this area at all.

Written by ArthurValiev in 823048022
The problem is that the code was reaching "3. additional setup" areas before recent changes.

The code structure was refactored a bit and the code following the intended flow by pure accident :)

So this "3. additional setup" area was not necessary for the functionality of the process. But with the recent refactoring, it became a must for process to finish initializing properly.

So mystery solved. No issues with dispatch_main. It's business logic of the product that relied on implicit behavior before which was now straightened out.

Thanks for being the voice of reason :)

Unexpected behavior of dispatch_main on macOS
 
 
Q