launchd:在GCD托管的信号处理程序中睡眠

问题描述:

当我尝试睡在SIGTERM处理程序中时,我遇到了一个奇怪的情况,该程序由Grand Central Dispatch管理,如here所述。
一切工作正常,当我不睡在SIGTERM处理程序中时,我在收到SIGKILL之前得到了一个SIGTERM信号处理程序。但是,只要我睡觉 - 即使是极其短的时间,比如usleep(1); - 我根本没有得到SIGTERM处理程序,但是我的守护程序通过launchd立即被SIGKILL处理。launchd:在GCD托管的信号处理程序中睡眠

顺便说一句我在我的plist文件中使用EnableTransactions和​​描述的正确vproc_transaction_begin(3)/vproc_transaction_end(3)代码。

不睡在SIGTERM处理程序中对我来说不是一个选项,因为我需要轮询关于我的“客户端processessess”的信息,以了解它是否保存以结束守护进程。

在我看来好像有一些编译器标志负责直接接收SIGKILL(而不是预期的SIGTERM),只要我在信号处理程序中做了一些睡眠,因为当我睡眠时,我看不到任何输出我的SIGTERM处理程序的一切。然而,我期望看到调试打印到睡眠呼叫,但事实并非如此。

这里是我的plist文件:

<?xml version="1.0" encoding="UTF-8"?> 
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
    <plist version="1.0"> 
    <dict> 
      <key>Label</key> 
      <string>org.example.myTestD</string> 
      <key>ProgramArguments</key> 
      <array> 
        <string>/usr/sbin/myTestD</string> 
      </array> 

      <key>RunAtLoad</key> 
      <true/> 

      <key>KeepAlive</key> 
      <true/> 

      <key>ExitTimeOut</key> 
      <integer>0</integer> 

      <key>EnableTransactions</key> 
      <true/> 
    </dict> 
    </plist> 

这里是我的SIGTERM处理器。请注意,只要添加了睡眠(1),我就会看到任何输出。线。

static void mySignalHandler(int sigraised) 
{ 
    int fd = open("/tmp/myTestD.log", O_WRONLY | O_CREAT | O_APPEND, 0777); 
    if (fd <= 0) return; 

    dprintf(fd, "%s(): signal received = %d, sleeping some time ...\n", __func__, sigraised); 
    usleep(1); 
    dprintf(fd, "%s(): ... done\n", __func__); 

    dprintf(fd, "%s(): EXITING\n", __func__); 
    close(fd); 

    // transactionHandle is global variable assigned in daemon main 
    if (transactionHandle) vproc_transaction_end(NULL, transactionHandle); 

    exit(0); 
} 

非常感谢您的任何提示/答案!

克里斯

我觉得你的问题的关键是,你的plist中有这样的:

 <key>ExitTimeOut</key> 
     <integer>0</integer> 

为launchd.plist手册页说:

ExitTimeOut <整数>

启动时间等待发送SIGTERM信号和发送SIGKILL信号之间,当作业停止时 。默认值是系统定义的。零值为 解释为无穷大,不应使用,因为它可以永久停止系统 关闭 。

实验一下,看起来这个文本是不准确。根据经验,我观察到,如果该值设置为0,我会得到您所描述的行为(在收到TERM之后立即编辑该过程的地址为KILL,而不管任何未完成的已声明事务)。如果将此值更改为某个任意更大例如60的数字,我观察到我的TERM处理程序被调用,并有机会在退出之前进行清理。

由于您发布的链接描述了您是否使用了经典的信号处理或GCD,这并不完全清楚,但是如果您使用的是经典的UNIX信号处理,那么我还应该提及您已经调用了函数您的信号处理程序不在可以在信号处理程序中调用的函数列表中(dprintfusleep不在列表中)。但是您似乎更可能使用GCD。

发生到我的另一件事是,如果你使用vproc_transaction_begin/end到支架你在你的处理器等力所能及的工作项目,那么你会得到这种行为“免费”,而无需在信号处理程序在所有。完全可以想象,无论正常工作项目如何,都需要进行一些集中式清理工作,但如果这只是等待其他异步任务完成,则可能更简单。

不管怎样,万一有帮助,这是我用来测试这种情况下的代码:

#import <Foundation/Foundation.h> 

#import <vproc.h> 

static void SignalHandler(int sigraised); 
static void FakeWork(); 
static void Log(NSString* str); 

int64_t outstandingTransactions; 
dispatch_source_t fakeWorkGeneratorTimer; 

int main(int argc, const char * argv[]) 
{ 
    @autoreleasepool 
    { 
     // Set up GCD handler for SIGTERM 
     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_global_queue(0, 0)); 
     dispatch_source_set_event_handler(source, ^{ 
      SignalHandler(SIGTERM); 
     }); 
     dispatch_resume(source); 

     // Tell the standard signal handling mechanism to ignore SIGTERM 
     struct sigaction action = { 0 }; 
     action.sa_handler = SIG_IGN; 
     sigaction(SIGTERM, &action, NULL); 

     // Set up a 10Hz timer to generate "fake work" events 
     fakeWorkGeneratorTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0)); 
     dispatch_source_set_timer(fakeWorkGeneratorTimer, DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC); 
     dispatch_source_set_event_handler(fakeWorkGeneratorTimer, ^{ 
      // Dont add an event *every* time... 
      if (arc4random_uniform(10) >= 5) dispatch_async(dispatch_get_global_queue(0, 0), ^{ FakeWork(); }); 
     }); 
     dispatch_resume(fakeWorkGeneratorTimer); 

     // Start the run loop 
     while (1) 
     { 
      // The runloop also listens for SIGTERM and will return from here, so I'm just sending it right back in. 
      [[NSRunLoop currentRunLoop] run]; 
     } 
    } 

    return 0; 
} 

static void SignalHandler(int sigraised) 
{ 
    // Open a transaction so that we dont get killed before getting to the end of this handler 
    vproc_transaction_t transaction = vproc_transaction_begin(NULL); 

    // Turn off the fake work generator 
    dispatch_suspend(fakeWorkGeneratorTimer); 

    Log([NSString stringWithFormat: @"%s(): signal received = %d\n", __func__, sigraised]); 

    int64_t transCount = outstandingTransactions; 
    while (transCount > 0) 
    { 
     Log([NSString stringWithFormat: @"%s(): %lld transactions outstanding. Waiting...\n", __func__, transCount]); 
     usleep(USEC_PER_SEC/4); 
     transCount = outstandingTransactions; 
    } 

    Log([NSString stringWithFormat: @"%s(): EXITING\n", __func__]); 

    // Close the transaction 
    vproc_transaction_end(NULL, transaction); 

    exit(0); 
} 

static void FakeWork() 
{ 
    static int64_t workUnitNumber; 

    const NSTimeInterval minWorkDuration = 1.0/100.0; // 10ms 
    const NSTimeInterval maxWorkDuration = 4.0; // 4s 

    OSAtomicIncrement64Barrier(&outstandingTransactions); 
    int64_t serialNum = OSAtomicIncrement64Barrier(&workUnitNumber); 
    vproc_transaction_t transaction = vproc_transaction_begin(NULL); 

    Log([NSString stringWithFormat: @"Starting work unit: %@", @(serialNum)]); 

    // Set up a callback some random time later. 
    int64_t taskDuration = arc4random_uniform(NSEC_PER_SEC * (maxWorkDuration - minWorkDuration)) + (minWorkDuration * NSEC_PER_SEC); 
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, taskDuration), dispatch_get_global_queue(0, 0), ^{ 
     Log([NSString stringWithFormat: @"Finishing work unit: %@", @(serialNum)]); 
     vproc_transaction_end(NULL, transaction); 
     OSAtomicDecrement64Barrier(&outstandingTransactions); 
    }); 
} 

static void Log(NSString* str) 
{ 
    static NSObject* lockObj = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     lockObj = [NSObject new]; 
    }); 

    @synchronized(lockObj) 
    { 
     int fd = open("/tmp/myTestD.log", O_WRONLY | O_CREAT | O_APPEND, 0777); 
     if (fd <= 0) return; 
     dprintf(fd, "%s\n", str.UTF8String); 
     close(fd); 
    } 
} 

而且plist中:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
<plist version="1.0"> 
    <dict> 
     <key>Label</key> 
     <string>DaemonDeathTest</string> 
     <key>ProgramArguments</key> 
     <array> 
      <string>/tmp/bin/DaemonDeathTest</string> 
     </array> 

     <key>RunAtLoad</key> 
     <true/> 

     <key>KeepAlive</key> 
     <true/> 

     <key>ExitTimeOut</key> 
     <integer>60</integer> 

     <key>EnableTransactions</key> 
     <true/> 
    </dict> 
</plist> 
+0

感谢您的快速响应ipmcc: – 2014-09-23 11:56:29

+0

对不起,我以前不必要的评论,但我被拖走了会议,而写了更多的行... 无论如何: *我不使用标准的POSIX信号处理,因为我知道一个是限制在一小组可重入的libc函数。 *关于ExitTimeOut设置为0:至少launchd似乎认识到无限ExitTimeOut,因为它在system.log中表示(“process has infinite exit time ...”) - >但是,我会尽快检查您的建议我回来工作了。 – 2014-09-24 05:20:54

+0

是的。很显然,ExitTimeOut设置为0意味着无限,但实际上,至少在退出由'launchctl unload'触发时(这是我触发launchd来终止进程的方式),看起来行为是不同的。来源在这里:http://www.opensource.apple.com/source/launchd/launchd-442.26.2/src/core.c那里没有一个**吸烟枪,但它看起来像那里有些情况下,零的'exit_timeout'没有被特别防范,所以这不是不可想象的。如果是我,我只是不会使用'ExitTimeOut = 0'并继续生活。 – ipmcc 2014-09-24 14:36:36