2014-09-21 31 views
0

當我嘗試睡在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); 
} 

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

克里斯

回答

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