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
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.
I think here is where your premise is a wrong. The part of contract you mentioned is still maintained by the pthread_exit()
function, as stated by IEEE Std 1003.1:
The
pthread_exit()
function cannot return to its caller.