Deadlock in thread_suspend() under Rosetta

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

Deadlock in thread_suspend() under Rosetta
 
 
Q