Hello,
We're seeing some strange crashes and noticed the following. It's unclear if related or not.
The contract for xpc_main
, which internally calls dispatch_main
, is This function never returns.
and they are appropriately peppered with __attribute__((__noreturn__))
. Documentation states:
This function “parks” the main thread and waits for blocks to be submitted to the main queue.
However, internally, dispatch_main
calls pthread_exit
. pthread_exit
's documentation states that:
After a thread has terminated, the result of access to local (auto) variables of the thread is undefined. Thus, references to local variables of the exiting thread should not be used for the
pthread_exit()
value_ptr
parameter value.
I'd say the two contracts of This function never returns.
and thread exiting with its storage released are diametrically opposed and can create nuanced issues.
Consider the following code:
struct asd {
int a;
};
struct asd* ptr;
void fff(void* ctx)
{
while(true)
{
printf("%d\n", ptr->a);
ptr->a = (ptr->a + 1);
usleep(100000);
}
}
int main(int argc, const char * argv[]) {
struct asd zxc;
zxc.a = 1;
ptr = &zxc;
dispatch_async_f(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), NULL, fff);
dispatch_main();
return 0;
}
This is a gross over-simplification of the code we have, but in the same "spirit". We have a C++ object that is created on the stack and exposes one of its members as a global pointer, with the assumption that it would never release. What I understand from This function never returns
is that the calling thread remains dormant and its stack remains alive. What I understand from pthread_exit
is that the thread is killed (this is verified with a debugger attached) and its stack storage is released.
Another thing that is throwing me off is that no sanitizer that is provided by clang/Xcode catches this issue. I don't see any special handling of the internal pthread_t
in libdispatch to keep the stack storage alive.
Our code is more complex, but can be solved by allocating the initial object on the heap, rather than on the stack. But still I would like to understand if this is the expected behavior. Perhaps my preconception of __attribute__((__noreturn__))
is wrong, and accessing stack variables post call to a __attribute__((__noreturn__))
function is UB?
Thanks
There’s definitely some subtlety here O-:
First, xpc_main
only calls dispatch_main
by default. You can override that with the RunLoopType
property.
Second, dispatch_main
can terminate the main thread. That’s why the docs put parks in quotes [1]. Weirdly, that can leave you with a process that has no threads, which is a pretty unusual setup.
Perhaps … accessing stack variables post call to a __attribute__((__noreturn__)) function is UB?
That’s a good question. Honestly, I don’t know. There are two different types of no-return routines:
- Those that block indefinitely.
- Those that terminate the current execution context. This includes things like
abort
, but alsopthread_exit
.
I’ve never seen anyone explore that distinction properly.
Either way, your current path forward is clear. First, you can work around this problem either by changing how you allocate this value or by avoiding dispatch_main
.
Second, the fact that the sanitisers didn’t help you find this problem is bugworthy. Please post your bug number, just for the record.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] Note that the ‘proper’ documentation for this routine, the dispatch_main
man page, doesn’t use that term.