确保子进程在Cocoa中已停止

确保子进程在Cocoa中已停止,cocoa,nstask,Cocoa,Nstask,我正在编写一个应用程序,启动一个运行简单web服务器的子进程。我正在使用NSTask,并通过管道与之通信,一切似乎都很好。但是,如果我的程序崩溃,子进程将保持活动状态,下次启动应用程序时,旧子进程和新子进程之间会发生冲突。是否有任何方法可以确保当所属应用程序死亡时子进程死亡?您的应用程序委托可以实现 - (void)applicationWillTerminate:(NSNotification *)aNotification 消息,并在此处终止NSTask。但是,不能保证在崩溃期间调用此委托

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

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

- (void)applicationWillTerminate:(NSNotification *)aNotification
消息,并在此处终止NSTask。但是,不能保证在崩溃期间调用此委托

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

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

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

EPERM“请求进程的有效用户ID与调用方的有效用户ID不同,并且该进程不是调用进程的后代。”

在这个问题上有一个较新的线索,但据我所知,没有简单的解决办法


我在我的应用程序中尝试了Robert Pointon关于Cocoadev的建议。不过我还没来得及测试它

其思想是将任务的进程组设置为与启动任务的进程的进程组相同(注意:下面的代码基本上是从上面的线程中提取出来的)


以上这些都不起作用……即使是
launchd
,在所有这些糟糕的文档中,复杂性都有办法处理这种常见的场景。我不知道为什么苹果不只是用“母舰批准”的方式来运行后台进程,不管怎样。。我的解决办法是

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

  • 反过来,从脚本内部启动子流程

  • 监视脚本中的子进程和父进程

  • 这有双重目的。。它可以让你“密切关注孩子们…”,并在不幸的杀父事件(或可怕的车祸)中杀死僵尸孤儿。然后,您自己扣动触发器(您是shell脚本),您的进程表将是干净的。。好像你从未存在过。没有被阻止的端口,重新启动时没有冲突,没有应用商店拒绝。让我知道这是否有用


    更新:我制作了一个Xcode模板/daemon/project/无论什么都可以。去看看

    下面的代码示例应该对您有所帮助

    是从,

    #包括
    #包括
    #包括
    静态void noteProcDeath(CFFileDescriptorRef fdref、CFOptionFlags回调类型、void*信息){
    结构kevent-kev;
    int fd=cfFileDescriptorTargetNativeDescriptor(fdref);
    kevent(fd,NULL,0,&kev,1,NULL);
    //在此处对进程死亡采取行动
    printf(“pid为“%u”的进程已死亡\n”,(无符号int)kev.ident);
    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref);//CFFileDescriptorRef在本例中不再有用
    }
    //需要一个参数,一个要监视的整数pid
    int main(int argc,char*argv[]){
    如果(argc<2)退出(1);
    int fd=kqueue();
    结构kevent-kev;
    EV_集(&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(来源);
    //运行运行循环20秒
    CFRunLoopRunInMode(kCFRunLoopDefaultMode,20.0,false);
    返回0;
    }
    
    有一个
    -[NSConcreteTask setStartsNewProcessGroup:
    私有API,如果您传递
    ,则创建的
    NSTask
    实例将不会将子进程从当前进程的进程组中分离,并且它将在父进程死亡后立即死亡


    xctool的荣誉:

    您是否尝试过使您的程序不崩溃我在考虑Atlas这里^^你可以尝试在Launchd中运行web服务器。至少这样,当你的应用程序崩溃并重新启动时,Launchd会告诉你服务器已经在运行,这样你就可以关闭它并在需要时重新启动它。
        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
    }
    
    #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;
    }