9

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

4

5 回答 5

2

以上都不起作用……即使launchd是糟糕的文档复杂性也没有办法处理这种常见情况。我不知道为什么 Apple 不只是采用“母公司认可”的方式来运行后台进程,但无论如何……我的解决方案是……

  1. 通过 NSTask 启动一个 shell 脚本并将你需要的任何变量传递给它。 还通过等传递您的父进程的 PID 。通过 int masterPID = [[NSProcessInfo processInfo] processIdentifier];$1、$2 等在您的脚本中读取这些。

  2. 反过来,从脚本内部启动您的子流程..

  3. 监视脚本中的子进程父进程。

这有双重目的……它使您能够“照看孩子……”,并在父母杀父(或可怕的车祸)的悲惨事件中 - 杀死僵尸孤儿。然后,您自己扣动扳机(您是 shell 脚本),您的进程表将是干净的……就好像您从未存在过一样。没有阻塞端口,没有重新启动冲突,没有应用商店拒绝。让我知道这是否有帮助!

更新:我制作了一个 Xcode 模板/守护进程/项目/任何可以解决问题的方法。检查出来.. mralexgray / Infanticide。

于 2012-01-07T21:07:27.770 回答
1

以下代码示例应该对您有所帮助。

是从这里借来的,

#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;
}
于 2013-04-16T11:28:05.277 回答
1

您的应用程序委托可以实现

- (void)applicationWillTerminate:(NSNotification *)aNotification

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

您可以采取两个额外的步骤:

  • 在启动新父进程期间关闭现有的孤立子进程,方法是在创建时将子进程的 PID 写到磁盘并在正常关闭期间将其删除(有时不是最安全的行为)。
  • 如果 NSPipe 的端点在特定的时间内没有发送数据(类似于心跳),则关闭子进程。
于 2009-08-18T19:42:22.220 回答
0

更新:现在我去正确地检查它是行不通的。尝试设置进程组失败并出现此错误;

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
}
于 2009-08-19T06:24:06.907 回答
0

-[NSConcreteTask setStartsNewProcessGroup:]私有API,如果你通过NO,那么创建NSTask的实例不会从当前进程的进程组中分离子进程,并且一旦父进程死亡它就会死亡。

感谢 xctool:https ://github.com/facebook/xctool/pull/159/files

于 2018-08-17T12:08:23.763 回答