本文介绍了发射:在GCD托管信号处理程序中休眠的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我尝试在由Grand Central Dispatch管理的SIGTERM处理程序中睡眠时,我在启动的托管守护程序中遇到了一种奇怪的情况,如。

一切正常,在收到SIGKILL之前,我确实得到了SIGTERM信号处理程序。不要在SIGTERM处理程序中睡觉。但是,一旦我入睡,即使是极短的时间,例如 usleep(1); ,我也根本不会得到SIGTERM处理程序,而是

I encounter a strange situation in a launchd managed daemon when I try to sleep in the SIGTERM handler that is managed with Grand Central Dispatch as described here.
Everything works fine and I do get a SIGTERM signal handler before receiving a SIGKILL when I do not sleep in the SIGTERM handler. But as soon as I do sleep -- even for extremly short amounts of time like a usleep(1); -- I do not get a SIGTERM handler at all but instead my daemon is SIGKILLed instantly by launchd.

顺便说一句,我在plist文件中使用EnableTransactions,并在 vproc_transaction_begin(3)中使用了正确的 / vproc_transaction_end(3)代码,如。

Btw I am using EnableTransactions in my plist file and the proper vproc_transaction_begin(3)/vproc_transaction_end(3) code as described here.

对于我来说,不选择不睡在SIGTERM处理程序中是因为我需要查询有关客户端进程的信息,以了解是否保存该文件以结束守护程序或不。

Not sleeping in the SIGTERM handler is not an option for me because I need to poll information about my "client processess" to know if it is save to end the daemon or not.

在我看来,似乎有些编译器标志负责在我在信号处理程序中进行一些睡眠后直接接收SIGKILL(而不是预期的SIGTERM)因为当我睡觉时,我根本看不到SIGTERM处理程序的任何输出。但是,我希望看到调试打印到睡眠调用,但事实并非如此。

It seems to me as if there is some compiler flag responsible for directly receiving the SIGKILL (and not the expected SIGTERM) as soon as I do some sleep in the signal handler because when I do sleep I do not see any outputs of my SIGTERM handler at all. I would however expect to see the debug prints up to the sleep call but this is not the case.

这是我的plist文件:

Here is my plist file:

  <?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)之后,我什么都看不到。

And here is my SIGTERM handler. Please note that I do see any output at all as soon as I add the usleep(1); line.

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);
}

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

Thank you very much for any hints/answers!

Chris

推荐答案

我认为问题的症结在于您在plist中有此问题:

I think the crux of your issue is that you have this in the plist:

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

launched.plist的手册页显示:

The man page for launchd.plist says:

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

The amount of time launchd waits between sending the SIGTERM signal and before sending a SIGKILL signal when the job is to be stopped. The default value is system-defined. The value zero is interpreted as infinity and should not be used, as it can stall system shutdown forever.

进行了一些实验,似乎该文本不准确。根据经验,我观察到如果将该值设置为 0 ,则会得到您正在描述的行为(过程为 KILL 在收到 TERM 之后立即被执行,而不考虑任何未完成的已声明交易。)如果我将此值更改为任意大的数字,例如60,则观察到我的 TERM 处理程序被调用,有机会在退出前进行清理。

Experimenting a bit, it appears that this text is not accurate. Empirically, I observe that if that value is set to 0, I get the behavior you're describing (where the process is KILLed immediately after receiving TERM, regardless of any outstanding declared transactions.) If I change this value to some arbitrary larger number like, say, 60, I observe my TERM handler being called and having a chance to do cleanup before exiting.

由于您发布的链接同时描述了这两种方法,因此尚不清楚是使用经典信号处理还是GCD,但是如果您使用经典UNIX信号处理,则我还应该提到,您在信号处理程序中调用的函数不在可以在信号处理程序中调用的函数列表中( dprintf usleep 不在列表中。)但是,您使用的是GCD的可能性更大。

It's not entirely clear whether you're using the classic signal handling or GCD since the link you posted describes both, but if you're using classic UNIX signal handling, then I should also mention that you've called functions in your signal handler that aren't on the list of functions that are OK to call in signal handlers (dprintf and usleep aren't on the list.) But it seems more likely that you're using GCD.

另一件事发生了对我来说,如果您使用 vproc_transaction_begin / end 将处理程序中正在等待的任何工作项括起来,那么您将免费获得此行为,而无需信号处理程序。完全可以想象,不管正常的工作项目是什么,您都需要做一些集中的清理工作,但是如果这只是为了等待其他异步任务完成,它甚至会更简单。

Another thing that occurs to me is that if you were using vproc_transaction_begin/end to bracket whatever work items you're waiting for in your handler, then you would get this behavior "for free" without needing the signal handler at all. It's completely conceivable that there is some centralized cleanup work that you need to do irrespective of normal work items, but if this is just about waiting for other asynchronous tasks to finish, it could be even simpler.

无论如何,如果有帮助,这是我用来测试这种情况的代码:

Anyway, in case it helps, here's the code I used to test this scenario:

#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>

这篇关于发射:在GCD托管信号处理程序中休眠的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-29 23:50