Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure a subprocess is dead in Cocoa

Tags:

cocoa

nstask

I'm writing an application that kicks off a subprocess running a simple web server. I am using NSTask and communicating with it with pipes, and everything seems more or less fine. However, if my program crashes, the subprocess is left alive and the next time I launch the app there is a conflict between the old subprocess and the new one. Is there any way to ensure that subprocesses die when the owning app dies?

like image 440
Francisco Ryan Tolmasky I Avatar asked Aug 18 '09 19:08

Francisco Ryan Tolmasky I


3 Answers

None of the above works… Not even launchd in all it's crappily-documented-complexity has a way to handle this common scenario. I dunno why Apple doesn't just make a "mothership-approved" way to run background processes, but whatever.. my solution is…

  1. Launch a shell script via NSTask and pass it any variables you need. Also pass in your parent process' PID via int masterPID = [[NSProcessInfo processInfo] processIdentifier]; etc. Read these in your script via $1, $2, etc.

  2. In turn, launch your subprocesses from inside the script..

  3. Monitor both the subprocess AND your parent process within the script.

This serves a dual purpose.. it enables you to "keep an eye on the kids..", and in the sad event of parentcide (or horrible car accident) - kill-off the zombie orphans. Then, you pull the trigger on yourself (you being the shell script) and your process table will be clean.. as if you never existed. No blocked ports, no conflicts on relaunch, no app-store rejections. Lemme know if this helps!

Update: I made a Xcode template / daemon / project / whatever that does the trick. Check it out.. mralexgray / Infanticide.

like image 155
Alex Gray Avatar answered Nov 14 '22 03:11

Alex Gray


Your application delegate can implement the

- (void)applicationWillTerminate:(NSNotification *)aNotification

message, and terminate the NSTask there. However, it is not guaranteed that during a crash, this delegate will be called.

Two additional steps you can take:

  • Shutdown an existing, orphaned subprocess during the launch of a new parent-process by writing down to disk the PID of the subprocess on creation and removing it during normal shutdown (sometimes not the safest behavior).
  • Shutdown the subprocess if the NSPipe's end-point didn't send data for a specific amount of time (something like a heartbeat).
like image 34
Aviad Ben Dov Avatar answered Nov 14 '22 03:11

Aviad Ben Dov


Following code sample should help you.

it is borrowed from here,

#include <CoreFoundation/CoreFoundation.h>
#include <unistd.h>
#include <sys/event.h>
static void noteProcDeath(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) {
    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    printf("process with pid '%u' died\n", (unsigned int)kev.ident);
    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example
}
// one argument, an integer pid to watch, required
int main(int argc, char *argv[]) {
    if (argc < 2) exit(1);
    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, atoi(argv[1]), EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
    // run the run loop for 20 seconds
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20.0, false);
    return 0;
}
like image 1
RLT Avatar answered Nov 14 '22 05:11

RLT