From d415fd64797b8de9c30f6a8947cdffac1327115c Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Wed, 15 May 2019 13:02:10 +0100 Subject: [PATCH] improve wait time for termination --- ChangeLog | 9 +++++- EcCommand.m | 91 +++++++++++++++++++++++++++++++++++++---------------- EcProcess.h | 7 +++-- Terminate.m | 90 ++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 142 insertions(+), 55 deletions(-) diff --git a/ChangeLog b/ChangeLog index 99fe74e..97ce99b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,15 @@ +2019-05-14 Richard Frith-Macdonald + + * EcProcess.h: + * EcCommand.m: Replace -terminate with -terminate: method. + * Terminate.m: Extend termination to control the time allowed for the + graceful shutdown (default to 30 seconds). + 2019-05-14 Richard Frith-Macdonald * EcProcess.h: * EcCommand.m: New method to return count of active clients. - * terminate.m: New help output and option to wait until Command + * Terminate.m: New help output and option to wait until Command server shuts down (printing out number of active clients). 2019-05-09 Richard Frith-Macdonald diff --git a/EcCommand.m b/EcCommand.m index 81f7660..5d7e92e 100644 --- a/EcCommand.m +++ b/EcCommand.m @@ -400,6 +400,7 @@ static NSMutableDictionary *launchInfo = nil; NSMutableDictionary *launching; unsigned pingPosition; NSTimer *terminating; + NSDate *terminateBy; NSDate *lastUnanswered; unsigned fwdSequence; unsigned revSequence; @@ -462,7 +463,8 @@ static NSMutableDictionary *launchInfo = nil; transient: (BOOL)t; - (void) reply: (NSString*) msg to: (NSString*)n from: (NSString*)c; - (NSArray*) restartAll: (NSString*)from; -- (void) terminate; +- (void) terminate: (NSDate*)by; +- (void) _terminate: (NSTimer*)t; - (void) timedOut: (NSTimer*)t; - (void) _timedOut: (NSTimer*)t; - (void) timeoutSoon; @@ -2087,6 +2089,7 @@ static NSMutableDictionary *launchInfo = nil; RELEASE(launchOrder); RELEASE(environment); RELEASE(lastUnanswered); + RELEASE(terminateBy); [super dealloc]; } @@ -3302,49 +3305,83 @@ static NSMutableDictionary *launchInfo = nil; * Tell all our clients to quit, and wait for them to do so. * If called while already terminating ... force immediate shutdown. */ -- (void) terminate: (NSTimer*)t +- (void) _terminate: (NSTimer*)t { - if (nil == terminating) + NSTimeInterval ti = [terminateBy timeIntervalSinceNow]; + + if ([clients count] == 0 && [launching count] == 0) { - [self information: @"Handling shutdown.\n" + [self information: @"Final shutdown." from: nil to: nil type: LT_CONSOLE]; + [terminating invalidate]; + terminating = nil; + [self cmdQuit: tStatus]; } - - if (nil == terminating) + else if (ti <= 0.0) { - terminating = [NSTimer scheduledTimerWithTimeInterval: 10.0 - target: self selector: @selector(terminate:) - userInfo: [NSDate new] - repeats: YES]; + [[self cmdLogFile: logname] puts: @"Final shutdown.\n"]; + [terminating invalidate]; + terminating = nil; + [self killAll]; + [self cmdQuit: tStatus]; } - - [self quitAll]; - - if (t != nil) + else { - NSDate *when = (NSDate*)[t userInfo]; - - if ([when timeIntervalSinceNow] < -30.0) - { - [[self cmdLogFile: logname] - puts: @"Final shutdown.\n"]; - [terminating invalidate]; - terminating = nil; - [self killAll]; - [self cmdQuit: tStatus]; - } + [self quitAll]; + terminating = [NSTimer scheduledTimerWithTimeInterval: ti + target: self + selector: _cmd + userInfo: nil + repeats: NO]; } } -- (void) terminate +- (void) terminate: (NSDate*)by { + NSTimeInterval ti = 30.0; + + if (nil != terminateBy) + { + NSString *msg; + + msg = [NSString stringWithFormat: @"Terminate requested," + @" but already terminating by %@", terminateBy]; + [self information: msg + from: nil + to: nil + type: LT_CONSOLE]; + return; + } + if (nil != by) + { + ti = [by timeIntervalSinceNow]; + if (ti < 0.5) + { + ti = 0.5; + by = nil; + } + else if (ti > 900.0) + { + ti = 900.0; + by = nil; + } + } + if (nil == by) + { + by = [NSDate dateWithTimeIntervalSinceNow: ti]; + } + ASSIGN(terminateBy, by); [self information: @"Terminate initiated.\n" from: nil to: nil type: LT_CONSOLE]; - [self terminate: nil]; + terminating = [NSTimer scheduledTimerWithTimeInterval: 0.01 + target: self + selector: @selector(_terminate:) + userInfo: nil + repeats: NO]; } - (void) timedOut: (NSTimer*)t diff --git a/EcProcess.h b/EcProcess.h index 253a649..68439fa 100644 --- a/EcProcess.h +++ b/EcProcess.h @@ -139,10 +139,11 @@ typedef enum { from: (NSString*)c; /** Shut down the Command server and all its clients.
- * Clients which fail to shut down gracefully within 30 seconds - * may be killed. + * Clients which fail to shut down gracefully before the specified timestamp + * will be forcibly killed. The timestamp is constrained to be at least half + * a second in the future and not more than 15 minutes in the future. */ -- (oneway void) terminate; +- (oneway void) terminate: (NSDate*)byDate; /** This is meant to be used remotely by all sorts of software running * on the machine and which is *not* a full Command client (ie, not a diff --git a/Terminate.m b/Terminate.m index 91221c8..c97919f 100644 --- a/Terminate.m +++ b/Terminate.m @@ -55,6 +55,7 @@ main() NSString *name; id proxy; BOOL any = NO; + int res = 0; [EcProcess class]; // Force linker to provide library @@ -70,13 +71,21 @@ main() || [[[NSProcessInfo processInfo] arguments] containsObject: @"--Help"] || [[[NSProcessInfo processInfo] arguments] containsObject: @"--help"]) { - printf("Terminate processes and Command server\n"); + printf("Terminate the Command server and its client processes.\n"); printf(" -CommandHost N\tuse alternative Command server host.\n"); printf(" -CommandName N\tuse alternative Command server name.\n"); - printf(" -Wait YES\tWait until termination completes.\n"); + printf(" -Wait seconds\tWait with completion time limit.\n"); printf(" -WellKnownHostNames '{...}'\tprovide a host name map.\n"); + printf("\n"); + printf(" By default a 30 second shutdown is requested and the\n"); + printf(" command finishes without waiting for it to complete.\n"); + printf(" Possible exit statuses are:\n"); + printf(" 0 termination requested (completed if -Wait was used).\n"); + printf(" 1 termination had not completed by end of -Wait timeout.\n"); + printf(" 2 the Command server was not found (maybe not running).\n"); + printf(" 3 this help was provided and no termination was requested.\n"); fflush(stdout); - exit(0); + exit(3); } dict = [defs dictionaryForKey: @"WellKnownHostNames"]; @@ -116,29 +125,53 @@ main() if (nil == proxy) { NSLog(@"Unable to contact %@ on %@", name, host); + res = 2; } else { + NSConnection *c = [proxy connectionForProxy]; unsigned active = [proxy activeCount]; + NSTimeInterval seconds = [defs doubleForKey: @"Wait"]; + NSDate *by; - [(id)proxy terminate]; - if ([defs boolForKey: @"Wait"]) + if (isnan(seconds) || 0.0 == seconds) { - NS_DURING + by = nil; + } + else if (seconds < 0.5) + { + seconds = 0.5; + } + else if (seconds > 900.0) + { + seconds = 900.0; + } + by = [NSDate dateWithTimeIntervalSinceNow: seconds]; + [(id)proxy terminate: by]; + if (nil == [defs objectForKey: @"Wait"]) + { + [c invalidate]; // No waiting + } + else + { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + /* Allow a second more than the requested shutdown time, + * so minor timing differences do not cause us to report + * the shutdown as having failed. + */ + while ([c isValid] && [by timeIntervalSinceNow] > -1.0) { - NSConnection *c = [proxy connectionForProxy]; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; + NSDate *delay; - while ([c isValid]) + [pool release]; + pool = [NSAutoreleasePool new]; + delay = [NSDate dateWithTimeIntervalSinceNow: 0.2]; + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: delay]; + if ([c isValid]) { - NSDate *delay; - - [pool release]; - pool = [NSAutoreleasePool new]; - delay = [NSDate dateWithTimeIntervalSinceNow: 0.2]; - [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode - beforeDate: delay]; - if ([c isValid]) + NS_DURING { unsigned remaining = [proxy activeCount]; @@ -149,17 +182,26 @@ main() fflush(stdout); } } + NS_HANDLER + { + /* An exception could occur if we lost the connection + * while trying to check the active count. In that + * case we can assume the Command server terminated. + */ + [c invalidate]; + active = 0; + } + NS_ENDHANDLER } - [pool release]; } - NS_HANDLER - { - NSLog(@"%@", localException); - } - NS_ENDHANDLER + [pool release]; + } + if (YES == [c isValid]) + { + res = 1; // Command did not shut down in time. } } RELEASE(arp); - return 0; + return res; }