确保Cocoa中的子进程已经死了

时间:2009-08-18 19:33:45

标签: cocoa nstask

我正在编写一个启动运行简单Web服务器的子进程的应用程序。我正在使用NSTask并使用管道与它进行通信,而且一切似乎都或多或少。但是,如果我的程序崩溃,子进程将保持活动状态,下次启动应用程序时,旧的子进程与新进程之间会发生冲突。有没有办法确保当拥有的应用程序死亡时子进程死亡?

5 个答案:

答案 0 :(得分:2)

上述所有工作都没有......甚至launchd甚至都没有记录 - 复杂性有一种处理这种常见情况的方法。我不知道为什么Apple不只是制作“母舰认可”的方式来运行后台进程,但无论如何......我的解决方案是......

  1. 通过NSTask启动shell脚本并将所需的任何变量传递给它。 也可以通过int masterPID = [[NSProcessInfo processInfo] processIdentifier];等传递您的父进程'PID 等。通过$ 1,$ 2等在您的脚本中阅读这些内容。

  2. 反过来,从脚本中的启动子进程 ..

  3. 在脚本中监控子进程 AND 您的父进程。

  4. 这有双重目的......它可以让你“关注孩子们......”,并且在父母(或可怕的车祸)的悲惨事件中 - 杀死僵尸孤儿。然后,你自己扣动扳机(你是shell脚本),你的流程表将是干净的.. 好像你从未存在过。没有阻塞端口,重新启动时没有冲突,没有应用商店拒绝。 Lemme知道这有用吗!

    更新:我制作了一个Xcode模板/守护进程/项目/无论做什么。看看.. mralexgray / Infanticide.

答案 1 :(得分:1)

您的申请代表可以实施

- (void)applicationWillTerminate:(NSNotification *)aNotification

消息,并在那里终止NSTask。但是,无法保证在崩溃期间,将调用此委托。

您可以采取以下两个步骤:

  • 在启动新的父进程期间关闭现有的孤立子进程,方法是在创建时将子进程的PID写入磁盘并在正常关闭期间将其删除(有时不是最安全的行为)。
  • 如果NSPipe的终点未在特定时间内发送数据(如心跳),请关闭子流程。

答案 2 :(得分:1)

以下代码示例可以为您提供帮助。

它来自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;
}

答案 3 :(得分:0)

更新:现在我去检查一下它不起作用。尝试设置进程组失败,出现此错误;

EPERM“请求进程的有效用户ID与调用者的有效用户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
}

答案 4 :(得分:0)

-[NSConcreteTask setStartsNewProcessGroup:]私有API,如果您传递NO,则创建的NSTask实例不会将子进程与当前进程的进程组分离,并且它将在不久后死亡父进程死亡。

xctool的荣誉:https://github.com/facebook/xctool/pull/159/files