2014-09-21 1 views
0

Je rencontre une situation étrange dans un démon géré launchd lorsque j'essaie de dormir dans le gestionnaire SIGTERM géré avec Grand Central Dispatch comme décrit here.
Tout fonctionne correctement et j'obtiens un gestionnaire de signal SIGTERM avant de recevoir un SIGKILL lorsque je ne dors pas dans le gestionnaire SIGTERM. Mais dès que je dors - même pour des temps extrêmement courts comme un usleep(1); - je ne reçois pas de gestionnaire SIGTERM du tout mais mon démon est SIGKILLé instantanément par launchd. Btw J'utilise EnableTransactions dans mon fichier plist et le code vproc_transaction_begin(3)/vproc_transaction_end(3) approprié comme décrit here.launchd: mise en veille dans le gestionnaire de signal GCD géré

Ne pas dormir dans le gestionnaire SIGTERM n'est pas une option pour moi car j'ai besoin d'interroger les informations sur mon "processus client" pour savoir s'il est nécessaire de mettre fin au démon ou non.

Il me semble qu'il y a un drapeau de compilateur chargé de recevoir directement le SIGKILL (et non le SIGTERM attendu) dès que je fais un peu de sommeil dans le gestionnaire de signal car quand je dors je ne vois aucune sortie de mon gestionnaire SIGTERM du tout. Je m'attendrais cependant à voir le débogage imprimer jusqu'à l'appel du sommeil mais ce n'est pas le cas.

Voici mon fichier 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> 

Et voici mon gestionnaire SIGTERM. S'il vous plaît noter que je vois toute sortie du tout dès que j'ajoute l'usleep (1); ligne.

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

Merci beaucoup pour vos conseils/réponses!

Chris

Répondre

0

Je pense que le point crucial de votre problème est que vous avez cela dans le plist:

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

La page de manuel pour launchd.plist dit:

ExitTimeOut <entier>

La durée d'attente du lancement attend entre l'envoi du signal SIGTERM et avant l'envoi d'un signal SIGKILL lorsque le travail doit être arrêté. La valeur par défaut est définie par le système. La valeur zéro est interprété comme infini et ne doit pas être utilisé, car il peut bloquer le système pour toujours .

Expérimenter un peu, il semble que ce texte est pas exact. Empiriquement, j'observe que si cette valeur est définie à 0, j'obtiens le comportement que vous décrivez (où le processus est KILL éd immédiatement après avoir reçu TERM, indépendamment de toutes les transactions déclarées en suspens.) Si je change cette valeur arbitrairement plus grande nombre comme, disons, 60, j'observe mon gestionnaire TERM étant appelé et ayant une chance de faire le nettoyage avant de quitter.Il n'est pas tout à fait clair si vous utilisez le traitement de signal classique ou GCD puisque le lien que vous avez posté décrit les deux, mais si vous utilisez le traitement de signal UNIX classique, je devrais également mentionner que vous avez appelé des fonctions dans votre gestionnaire de signal qui ne figure pas dans la liste des fonctions qui peuvent être appelées dans les gestionnaires de signaux (dprintf et usleep ne figure pas dans la liste). Mais il semble plus probable que vous utilisiez GCD. Une autre chose qui me vient à l'esprit est que si vous utilisiez vproc_transaction_begin/end pour mettre entre parenthèses les éléments de travail que vous attendiez dans votre gestionnaire, vous obtiendriez ce comportement «gratuitement» sans avoir besoin du gestionnaire de signal. Il est tout à fait concevable qu'il y ait un travail de nettoyage centralisé que vous devez faire indépendamment des éléments de travail normaux, mais s'il s'agit simplement d'attendre que d'autres tâches asynchrones se terminent, cela pourrait être encore plus simple.

Quoi qu'il en soit, dans le cas où il aide, voici le code que je l'habitude de tester ce scénario:

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

Et le 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

Merci pour votre réponse rapide iPMCC: –

+0

Désolé pour mon précédent commentaires inutiles mais j'ai été traîné à une réunion tout en écrivant quelques lignes ... De toute façon: * Je n'utilise pas la gestion de signal POSIX standard car je sais que l'on est limité à un petit ensemble de fonctions libc réentrant. * Concernant ExitTimeOut mis à 0: Au moins launchd semblait reconnaître l'infini ExitTimeOut parce qu'il l'avait dit dans le system.log ("le processus a une durée de sortie infinie ...") -> Cependant, je vais vérifier votre suggestion dès que Je suis de retour au travail. –

+0

Ouais. Clairement, ExitTimeOut mis à 0 est * destiné * à signifier infini, mais en pratique, au moins quand la sortie est déclenchée par 'launchctl unload' (c'est ainsi que je déclenchait launchd pour tuer le processus) il semble que le comportement soit différent. La source est ici: http://www.opensource.apple.com/source/launchd/launchd-442.26.2/src/core.c Il n'y a pas exactement un ** pistolet fumant ** là, mais il ressemble à là Dans certains cas, un «exit_timeout» de zéro n'est pas spécifiquement protégé, donc ce n'est pas inconcevable. Si c'était moi, je n'utiliserais pas 'ExitTimeOut = 0' et continuerais avec la vie. – ipmcc

Questions connexes