2014-09-21 5 views
0

here과 같이 Grand Central Dispatch로 관리되는 SIGTERM 처리기에서 잠자기를 시도 할 때 launchd 관리 데몬에서 이상한 상황이 발생합니다.
모든 것이 잘 작동하고 SIGTERM 신호 처리기에서 SIGTERM 처리기에서 절전 모드를 종료 할 때 SIGKILL을 수신합니다. 그러나 내가 잠들 자마자 - usleep(1);과 같이 극단적으로 짧은 시간 일지라도 - 나는 SIGTERM 핸들러를 전혀 얻지 못한다. 대신에 데몬은 launchd에 의해 즉시 SIGKILLED된다.launchd : GCD 관리되는 신호 처리기에서 잠시

Btw 내 plist 파일에 vproc_transaction_begin(3)/vproc_transaction_end(3) 코드로 EnableTransactions를 사용하고 있습니다 (here).

"클라이언트 processess"에 대한 정보를 폴링하여 데몬을 종료할지 여부를 알기 때문에 SIGTERM 처리기에서 절전 모드로 전환하지 않아도됩니다.

마치 잠을 자면 출력이 보이지 않기 때문에 시그널 핸들러에서 잠을 자 자마자 직접 SIGKILL (및 예상되는 SIGTERM이 아닌)을 수신하는 일부 컴파일러 플래그가있는 것처럼 보입니다. 내 SIGTERM 핸들러의 그러나 디버그가 절전 모드로 인쇄되는 것을 볼 수는 있지만, 그렇지는 않습니다. 여기

<?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 핸들러는 다음과 같습니다

여기 내 PLIST 파일입니다. 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); 
} 

모든 힌트/답변을 보내 주셔서 감사합니다.

크리스

답변

0

나는 문제의 핵심은 당신이 PLIST이있는 것으로 생각 :

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

launchd.plist에 대한 매뉴얼 페이지 말한다 :

ExitTimeOut < 정수 >

launchd가 SIGTERM 신호를 보내는 사이와 작업이 이 중지 될 때 SIGKILL 신호를 보내기 전에 기다리는 시간입니다. 기본값은 시스템 정의입니다. 값 0은 이며 무한대로 해석되므로 사용하지 않아야합니다. 종료 영원히 멈출 수 있습니다.

약간의 실험은,이 텍스트가 정확하지 것 같습니다. 경험적으로, 그 값이 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 : 당신의 빠른 응답 ipmcc에 대한

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

감사 :

어쨌든, 도움이 경우에, 여기에 내가이 시나리오를 테스트하는 데 사용되는 코드는 내 이전을위한 –

+0

죄송합니다 불필요한 코멘트가 있지만 더 많은 라인을 작성하는 동안 나는 회의에 끌려 갔다 ... 아무렇게나 : * 나는 재진입 libc 함수의 작은 세트로 제한된다는 것을 알고 있으므로 표준 POSIX 신호 처리를 사용하지 않는다. * ExitTimeOut을 0으로 설정 한 경우 적어도 launchd는 system.log ("프로세스는 무한 종료 시간이 있습니다 ...")에서>라고 말했기 때문에 무한 exitTimeOut을 인식하는 것으로 보였습니다. 그러나 즉시 제안을 확인합니다. 나는 일하러 돌아왔다. –

+0

예. 분명히 ExitTimeOut이 0으로 설정되면 무한히 의미하지만 * 실제로는 적어도 launchctl unload (프로세스를 죽이기 위해 launchd를 트리거하는 방법)에 의해 exit가 트리거 될 때 동작이 다르게 보입니다. 출처는 다음과 같습니다. http://www.opensource.apple.com/source/launchd/launchd-442.26.2/src/core.c 거기에 정확히 ** 흡연 총 **이 없지만 거기에있는 것처럼 보입니다. 'exit_timeout'의 제로가 명확하게 지켜지지 않는 경우도 있으므로 생각할 수 없습니다. 그것이 나 였다면, 나는 단지'ExitTimeOut = 0'을 사용하지 않고 삶을 살아갈 것입니다. – ipmcc

관련 문제