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>
Merci pour votre réponse rapide iPMCC: –
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. –
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