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?
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…
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.
In turn, launch your subprocesses from inside the script..
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.
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:
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With