multithreadingmacossignalsthread-synchronization

How can I wake a thread in macOS in a way that is async-signal-safe?


I have a GRPC server application I'm developing on macOS. In this application, I need to handle SIGINT and SIGTERM in order to terminate gracefully.

Normally, on Linux, I would use POSIX semaphores, as the functions involved to wait and notify using a POSIX semaphore are async-signal-safe.

However, in macOS, POSIX sempahores are not implemented. I've looked at other possible synchronization primitives to use and I've stumbled upon os_unfair_lock, which looks nice, but in the documentation it does not state that it is async-signal-safe.

How can I safely have a thread waiting on a condition variable be woken up from a signal handler without resorting to polling an atomic variable, which would be burning CPU?

Thanks

EDIT: The application is in C/C++


Solution

  • I've resorted to use pipe().

    1. Open the pipe before registering the signal handler
      int fds[2];
      if(pipe(fds) != 0) {
        LOG(ERROR) << "Unable to open pipe: " << strerror(errno) << std::endl;
        return 1;
      }
    
      struct sigaction newAction;
      newAction.sa_handler = signalHandler;
      sigemptyset(&newAction.sa_mask);
      newAction.sa_flags = 0;
      sigaction(SIGINT, &newAction, NULL);
      sigaction(SIGTERM, &newAction, NULL);
    
    1. From the thread that needs to be waken up, we can just call read() with blocking IO to not waste CPU cycles. Bear in mind the call can return if interrupted by a signal, hence why we check for EINTR:
    ssize_t bytes_read = read(pipeFD, &msg, 4);
    if(bytes_read == -1) {
      if(errno != EINTR) {
        LOG(ERROR) << "Error reading from pipe: " << strerror(errno) << std::endl;
        exitFlag = true;
        break;
      }
    }
    
    1. From the signal handler, we can write to the pipe. The write syscall is async-signal-safe:
    void signalHandler(int signal) {
      write(pipeFD, "EXIT", 4);
    }
    

    That does the trick. The user can validate the data sent by the pipe or not, according to pipe(2), when both file descriptors are closed any remaining data is discarded.