Hi all,
I'm working on a cross-platform runtime that manages a pool of threads (think game engine / emulator style... dozens of guest threads mapped 1:1 to host pthreads). It was originally written for Linux and Windows and we're now porting to macOS on Apple Silicon.
We've hit a wall with a deadlock on macOS and traced it back to our use of POSIX unnamed semaphores (sem_init / sem_wait / sem_post) for thread suspend and resume. We were unaware these have never actually been implemented on macOS, sem_init silently returns -1 with ENOSYS and then sem_wait just hangs forever. That explains our deadlock.
The tricky part is how we use them. Our suspend mechanism works by sending SIGUSR1 to a target thread via pthread_kill. The signal handler then calls sem_wait to block the thread in place until another thread calls sem_post to resume it. So whatever we replace sem_init/sem_wait with needs to be safe to call from inside a signal handler.
From what I can tell:
dispatch_semaphore_wait is not documented as async-signal-safe pthread_cond_wait is also not async-signal-safe os_sync_wait_on_address looks promising but requires macOS 14.4+ which is a pretty high floor We could spin on a std::atomic<bool> with .wait() / .notify_all() but I've seen reports of high wake latency (up to 15ms) in libc++'s implementation on macOS My questions:
What's the recommended way to block a thread inside a signal handler on macOS? Is there an async-signal-safe wait primitive I'm missing? Would restructuring to avoid blocking in the signal handler entirely be the better approach? For example, having the signal handler just set an atomic flag and then checking it at yield points — would that be the expected pattern on macOS? For the non-signal-handler suspend/resume paths, is dispatch_semaphore_t the right replacement for sem_t, or is there something better suited for high-frequency thread synchronization in 2026? Separately, we're also using ucontext (makecontext/swapcontext) for a fiber system on macOS and hitting issues on native arm64, it works under Rosetta but breaks natively. We have a setjmp/longjmp + manual stack pivot backend we can switch to. Is there any plan to fix or un-deprecate the ucontext functions on arm64, or should we just move off them permanently?