The thread_suspend()
Mach API will sometimes deadlock when called from an Intel process running on a Mac with Apple Silicon.
This can be replicated by the following code in a unit test class, and using Xcode's "Run Repeatedly..." functionality to run the test 1000 times:
- (void)testThreadDeadlock {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
const int threadCount = 1000;
for (int i = 0; i < threadCount; i++) {
[NSThread detachNewThreadSelector:@selector(waitForSemaphore:) toTarget:self withObject:semaphore];
}
thread_t thisThread = mach_thread_self();
thread_t *threads = NULL;
mach_msg_type_number_t numThreads = 0;
task_threads(mach_task_self(), &threads, &numThreads);
for (mach_msg_type_number_t i = 0; i < numThreads; i++) {
if (threads[i] != thisThread) {
thread_suspend(threads[i]); // <--- this call will sometimes deadlock
}
}
for (mach_msg_type_number_t i = 0; i < numThreads; i++) {
if (threads[i] != thisThread) {
thread_resume(threads[i]);
}
}
vm_deallocate(mach_task_self(), threads, sizeof(*threads) * numThreads);
for (int i = 0; i < threadCount; i++) {
dispatch_semaphore_signal(semaphore);
}
}
- (void)waitForSemaphore:(dispatch_semaphore_t)semaphore {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
Oddly, the deadlock does not appear to happen if the threads are spawned with pthread_create()
instead of +[NSThread detachNewThreadSelector:toTarget:withObject:]
This deadlock only happens under Rosetta. On Intel Macs or when running the ARM slice on Apple Silicon no deadlock can be reprodiced with this code.
A similar deadlock can also occur in thread_info()
if it is used after the threads are suspended.
This has also been submitted via Feedback Assistant - FB9922280