launchd helper unable to handle SIGTERM

I have a launchd helper process which constantly runs in the background, and when the user shuts down the machine, I need the helper to perform some clean up tasks before exiting. I've added a signal handler for SIGTERM in my helper code, like this:


signal(SIGTERM, signalHandler);

void signalHandler(int signal)
{
    terminationCleanup();
    exit(signal);
}


launchd documentation says that daemons should be sent a SIGTERM before a SIGKILL (my helper's launchd plist has ExitTimeOut set to 10, and EnableTransactions to false).

However, the helper process never gets the SIGTERM on shutdown. If I try to unload the helper manually using launchctl unload <plist>, the helper never gets the SIGTERM, and then 10 seconds later, the system logger shows: "Service did not exit 10 seconds after SIGTERM. Sending SIGKILL.".


Even if I try to do it manually by sending sudo kill -15 pid, the SIGTERM signal handler is not triggered.


At this point, you may think "well, obviously your signal handler isn't set up correctly"... well, I also am handling the SIGABRT signal, so what I found is that if I first do sudo kill -6 pid, my signal handler is triggered and I catch the SIGABRT properly. My helper launchd plist also has KeepAlive set to true, so the helper process will restart immediately after. Now, if I do kill -15 pid, or launchctl unload <plist>, now the SIGTERM is actually sent and caught by my helper.


So, it makes no sense. Why does my helper process only handle the SIGTERM signal after the process has been sent SIGABRT, and a new process is spawned?

Replies

What are you doing in the terminationCleanup() function? Signal handlers run in a very VERY restricted environment. There are very few functions that can be safely run from a signal handler (See man sigaction for a list). The best way to handle signals on OS X is to use GCD's dispatch sources (See Apple's "Concurrency Programming Guide" for details) since the dispatch source event handler does not have any of the restrictions of the signal handler.

Using dispatch sources your code would look something like the following:

// Ignore SIGTERM since we are using a GCD dispatch source to actually capture
// and handle the signal

signal(SIGTERM, SIG_IGN);


dispatch_source_t sigtermSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue());

dispatch_source_set_event_handler(sigtermSource, ^{
  terminationCleanup();
  exit(SIGTERM);
});


dispatch_resume(sigtermSource);

To reiterate the point made by mdobro, just the small snippet of code indicates that your signal handler is not legal:

exit
is not on the list of async signal safe functions (because it can call arbitrary code via callbacks registered via
atexit
). You should switch to the dispatch source model.

However, I don’t think that this is the cause of your actual problem. Regarding that, have you tried reproducing this with a small test project? The behaviour you’ve described is a bit strange and it’d be useful to know whether it’s dependent on the rest of the code in your launchd helper process.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
I encountered same problem, and what I found is sudden termination