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

时间:2014-09-21 12:14:38

标签: macos signals grand-central-dispatch daemon launchd

当我尝试在按照here所述的Grand Central Dispatch管理的SIGTERM处理程序中睡眠时,我在launchd托管守护程序中遇到一种奇怪的情况。
一切正常,当我没有在SIGTERM处理程序中休眠时,我在收到SIGKILL之前得到一个SIGTERM信号处理程序。但是一旦我睡觉 - 即使是像usleep(1);那样极短的时间 - 我根本没有得到SIGTERM处理程序,而是我的守护进程立即通过launchd进行了SIGKILL。

顺便说一下,我在plist文件中使用EnableTransactions,并使用here所述的正确vproc_transaction_begin(3) / vproc_transaction_end(3)代码。

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

在我看来好像有一些编译器标志负责直接接收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处理程序。请注意,我一添加usleep就会看到任何输出(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);
}

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

克里斯

1 个答案:

答案 0 :(得分:1)

我认为你的问题的症结在于你在plist中有这个:

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

launchd.plist的手册页说:

  

ExitTimeOut &lt; integer&gt;

     

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

尝试一下,看来此文字不准确。根据经验,我观察到,如果该值设置为0,我会得到您正在描述的行为(在收到KILL后,流程立即TERM,无论是否有任何未完成的声明如果我将此值更改为某个更大的数字,例如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>