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:
- 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).
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;
}
UPDATE: Now that I go to check this properly it doesn't work. Trying to set the process group fails with this error;
EPERM "The effective user ID of the requested process is different from that of the caller and the process is not a descendant of the calling process."
There is a more recent thread on this issue but no easy solution as far as I can tell
http://www.omnigroup.com/mailman/archive/macosx-dev/2009-March/062164.html
I've tried a suggestion by Robert Pointon on Cocoadev in my app. I haven't got around to testing it yet though.
http://www.cocoadev.com/index.pl?NSTaskTermination
The idea is to set the process group of the task to be the same as that of the process that launches the task (note: the code below is basically lifted from the thread above).
pid_t group = setsid();
if (group == -1) {
group = getpgrp();
}
[task launch];
if (setpgid([task processIdentifier], group) == -1) {
NSLog(@"unable to put task into same group as self");
[task terminate];
} else {
// handle running task
}
There's -[NSConcreteTask setStartsNewProcessGroup:]
private API, if you pass NO
, then created NSTask
instance will not detach child process from the process group of the current process, and it will die as soon as parent process dies.
Kudos to xctool: https://github.com/facebook/xctool/pull/159/files
© 2022 - 2024 — McMap. All rights reserved.