我正在编写一个启动运行简单 Web 服务器的子进程的应用程序。我正在使用 NSTask 并通过管道与它通信,一切似乎或多或少都很好。但是,如果我的程序崩溃,子进程会保持活动状态,并且下次启动应用程序时,旧子进程和新子进程之间会发生冲突。当拥有的应用程序死亡时,有什么方法可以确保子进程死亡?
5 回答
以上都不起作用……即使launchd
是糟糕的文档复杂性也没有办法处理这种常见情况。我不知道为什么 Apple 不只是采用“母公司认可”的方式来运行后台进程,但无论如何……我的解决方案是……
通过 NSTask 启动一个 shell 脚本并将你需要的任何变量传递给它。 还通过等传递您的父进程的 PID 。通过
int masterPID = [[NSProcessInfo processInfo] processIdentifier];
$1、$2 等在您的脚本中读取这些。反过来,从脚本内部启动您的子流程..
监视脚本中的子进程和父进程。
这有双重目的……它使您能够“照看孩子……”,并在父母杀父(或可怕的车祸)的悲惨事件中 - 杀死僵尸孤儿。然后,您自己扣动扳机(您是 shell 脚本),您的进程表将是干净的……就好像您从未存在过一样。没有阻塞端口,没有重新启动冲突,没有应用商店拒绝。让我知道这是否有帮助!
更新:我制作了一个 Xcode 模板/守护进程/项目/任何可以解决问题的方法。检查出来.. mralexgray / Infanticide。
以下代码示例应该对您有所帮助。
是从这里借来的,
#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;
}
您的应用程序委托可以实现
- (void)applicationWillTerminate:(NSNotification *)aNotification
消息,并在那里终止 NSTask。但是,不能保证在崩溃期间会调用此委托。
您可以采取两个额外的步骤:
- 在启动新父进程期间关闭现有的孤立子进程,方法是在创建时将子进程的 PID 写到磁盘并在正常关闭期间将其删除(有时不是最安全的行为)。
- 如果 NSPipe 的端点在特定的时间内没有发送数据(类似于心跳),则关闭子进程。
更新:现在我去正确地检查它是行不通的。尝试设置进程组失败并出现此错误;
EPERM "请求进程的有效用户ID与调用者不同,并且该进程不是调用进程的后代。"
关于这个问题有一个更新的线程,但据我所知没有简单的解决方案
http://www.omnigroup.com/mailman/archive/macosx-dev/2009-March/062164.html
我在我的应用程序中尝试了 Robert Pointon 关于 Cocoadev 的建议。不过我还没来得及测试它。
http://www.cocoadev.com/index.pl?NSTaskTermination
思路是将任务的进程组设置为与启动任务的进程相同(注意:下面的代码基本上是从上面的线程中提炼出来的)。
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
}
有-[NSConcreteTask setStartsNewProcessGroup:]
私有API,如果你通过NO
,那么创建NSTask
的实例不会从当前进程的进程组中分离子进程,并且一旦父进程死亡它就会死亡。
感谢 xctool:https ://github.com/facebook/xctool/pull/159/files