diff options
author | rowanbeentje <rowan@beent.je> | 2009-02-25 01:03:29 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-02-25 01:03:29 +0000 |
commit | 10a7c5a32983070158a736e479b2c6200ecd70fd (patch) | |
tree | bc8c6b5a30886449707b5464bacdc570bfa0e775 | |
parent | 27285b94decd7e3ce6e6600ee074e92bd0449f7d (diff) | |
download | sequelpro-10a7c5a32983070158a736e479b2c6200ecd70fd.tar.gz sequelpro-10a7c5a32983070158a736e479b2c6200ecd70fd.tar.bz2 sequelpro-10a7c5a32983070158a736e479b2c6200ecd70fd.zip |
Implement a connection keepalive, addressing Issue #171. This runs a ping every sixty seconds (a value stored in prefs, though not exposed via the prefs interface yet) in a background thread, to further improve connectivity handling following r334. Pings are only run when necessary - ie active use of the program extends the next ping interval as appropriate, so general impact should be minimal.
-rw-r--r-- | Source/CMMCPConnection.h | 7 | ||||
-rw-r--r-- | Source/CMMCPConnection.m | 83 | ||||
-rw-r--r-- | Source/MainController.m | 10 |
3 files changed, 91 insertions, 9 deletions
diff --git a/Source/CMMCPConnection.h b/Source/CMMCPConnection.h index 3a6b4016..a9b2554e 100644 --- a/Source/CMMCPConnection.h +++ b/Source/CMMCPConnection.h @@ -49,6 +49,9 @@ NSString *connectionHost; int connectionPort; NSString *connectionSocket; + + NSTimer *keepAliveTimer; + NSDate *lastKeepAliveSuccess; } - (id) init; @@ -65,5 +68,9 @@ - (void) setDelegate:(id)object; - (NSTimeZone *) timeZone; - (BOOL) pingConnection; +- (void) startKeepAliveTimerResettingState:(BOOL)resetState; +- (void) stopKeepAliveTimer; +- (void) keepAlive:(NSTimer *)theTimer; +- (void) threadedKeepAlive; @end diff --git a/Source/CMMCPConnection.m b/Source/CMMCPConnection.m index 20f5ba11..5319f42f 100644 --- a/Source/CMMCPConnection.m +++ b/Source/CMMCPConnection.m @@ -66,6 +66,8 @@ static void forcePingTimeout(int signalNumber); connectionHost = nil; connectionPort = 0; connectionSocket = nil; + keepAliveTimer = nil; + lastKeepAliveSuccess = nil; [NSBundle loadNibNamed:@"ConnectionErrorDialog" owner:self]; } @@ -92,6 +94,7 @@ static void forcePingTimeout(int signalNumber); mysql_options(mConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); } + [self startKeepAliveTimerResettingState:YES]; return [super connectWithLogin:login password:pass host:host port:port socket:socket]; } @@ -112,6 +115,8 @@ static void forcePingTimeout(int signalNumber); connectionPort = 0; if (connectionSocket) [connectionSocket release]; connectionSocket = nil; + + [self stopKeepAliveTimer]; } @@ -296,6 +301,8 @@ WARNING : incomplete implementation. Please, send your fixes. // If no connection is present, return nil. if (!mConnected) return nil; + [self stopKeepAliveTimer]; + // Check the connection. This triggers reconnects as necessary, and should only return false if a disconnection // has been requested - in which case return nil if (![self checkConnection]) return nil; @@ -322,12 +329,13 @@ WARNING : incomplete implementation. Please, send your fixes. return nil; } + + [self startKeepAliveTimerResettingState:YES]; + return [theResult autorelease]; } -static void sigalarm(int segnum) { - NSLog(@"BOOOOYAAAAA"); -} + /* * Checks whether the connection to the server is still active. If not, prompts for what approach to take, * offering to retry, reconnect or disconnect the connection. @@ -446,7 +454,7 @@ static void sigalarm(int segnum) { - (BOOL) pingConnection { struct sigaction timeoutAction; - NSDate *startDate = [NSDate date]; + NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; BOOL pingSuccess = FALSE; // Construct the SIGALRM to fire after the connection timeout if it isn't cleared, calling the forcePingTimeout function. @@ -473,7 +481,7 @@ static void sigalarm(int segnum) { // If the ping failed within a second, try another one; this is because a terminated-but-then // restored connection is at times restored or functional after a ping, but the ping still returns // an error. This additional check ensures the returned status is correct with minimal other effect. - if (!pingSuccess && ([[NSDate date] timeIntervalSinceDate:startDate] < 1)) { + if (!pingSuccess && ([startDate timeIntervalSinceNow] > -1)) { pingSuccess = (BOOL)(! mysql_ping(mConnection)); } } @@ -485,6 +493,7 @@ static void sigalarm(int segnum) { timeoutAction.sa_flags = 0; sigaction(SIGALRM, &timeoutAction, NULL); + [startDate release]; return pingSuccess; } @@ -498,4 +507,68 @@ static void forcePingTimeout(int signalNumber) longjmp(pingTimeoutJumpLocation, 1); } +/* + * Restarts a keepalive to fire in the future. + */ +- (void) startKeepAliveTimerResettingState:(BOOL)resetState +{ + if (keepAliveTimer) [self stopKeepAliveTimer]; + if (!mConnected) return; + + if (resetState && lastKeepAliveSuccess) { + [lastKeepAliveSuccess release]; + lastKeepAliveSuccess = nil; + } + + keepAliveTimer = [NSTimer + scheduledTimerWithTimeInterval:[[[NSUserDefaults standardUserDefaults] objectForKey:@"keepAliveInterval"] doubleValue] + target:self + selector:@selector(keepAlive:) + userInfo:nil + repeats:NO]; + [keepAliveTimer retain]; +} + +/* + * Stops a keepalive if one is set for the future. + */ +- (void) stopKeepAliveTimer +{ + if (!keepAliveTimer) return; + [keepAliveTimer invalidate]; + [keepAliveTimer release]; + keepAliveTimer = nil; +} + +/* + * Keeps a connection alive by running a ping. + */ +- (void) keepAlive:(NSTimer *)theTimer +{ + if (!mConnected) return; + + // If there a successful keepalive record exists, and it was more than 5*keepaliveinterval ago, + // abort. This prevents endless spawning of threads in a state where the connection has been + // cut but mysql doesn't pick up on the fact - see comment for pingConnection above. The same + // forced-timeout approach cannot be used here on a background thread. + // When the connection is disconnected in code, these 5 "hanging" threads are automatically cleaned. + if (lastKeepAliveSuccess && [lastKeepAliveSuccess timeIntervalSinceNow] < -5 * [[[NSUserDefaults standardUserDefaults] objectForKey:@"keepAliveInterval"] doubleValue]) return; + + [NSThread detachNewThreadSelector:@selector(threadedKeepAlive) toTarget:self withObject:nil]; + [self startKeepAliveTimerResettingState:NO]; +} + +/* + * A threaded keepalive to avoid blocking the interface + */ +- (void) threadedKeepAlive +{ + if (!mConnected) return; + mysql_ping(mConnection); + if (lastKeepAliveSuccess) { + [lastKeepAliveSuccess release]; + lastKeepAliveSuccess = nil; + } + lastKeepAliveSuccess = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; +} @end
\ No newline at end of file diff --git a/Source/MainController.m b/Source/MainController.m index f05d7046..c5e2eeb0 100644 --- a/Source/MainController.m +++ b/Source/MainController.m @@ -663,11 +663,13 @@ checks for updates and opens download page in default browser [NSNumber numberWithBool:YES], @"fetchRowCount", [NSNumber numberWithBool:YES], @"limitRows", [NSNumber numberWithInt:1000], @"limitRowsValue", + [NSNumber numberWithInt:60], @"keepAliveInterval", + [NSNumber numberWithInt:0], @"lastUsedVersion", nil]]; - // If no version number is present in prefs, and column widths have been saved, walk through - // them and remove any table widths set to 15 or less (fix for mangled columns caused by Issue #140) - if ([prefs objectForKey:@"lastUsedVersion"] == nil && [prefs objectForKey:@"tableColumnWidths"] != nil) { + // For versions prior to r336, where column widths have been saved, walk through them and remove + // any table widths set to 15 or less (fix for mangled columns caused by Issue #140) + if ([[prefs objectForKey:@"lastUsedVersion"] intValue] < 336 && [prefs objectForKey:@"tableColumnWidths"] != nil) { NSEnumerator *databaseEnumerator, *tableEnumerator, *columnEnumerator; NSString *databaseKey, *tableKey, *columnKey; NSMutableDictionary *newDatabase, *newTable; @@ -700,7 +702,7 @@ checks for updates and opens download page in default browser [prefs setObject:[NSDictionary dictionaryWithDictionary:newTableColumnWidths] forKey:@"tableColumnWidths"]; [newTableColumnWidths release]; } - + // Write the current bundle version to the prefs [prefs setObject:[NSNumber numberWithInt:currentVersionNumber] forKey:@"lastUsedVersion"]; |