aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-02-25 01:03:29 +0000
committerrowanbeentje <rowan@beent.je>2009-02-25 01:03:29 +0000
commit10a7c5a32983070158a736e479b2c6200ecd70fd (patch)
treebc8c6b5a30886449707b5464bacdc570bfa0e775
parent27285b94decd7e3ce6e6600ee074e92bd0449f7d (diff)
downloadsequelpro-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.h7
-rw-r--r--Source/CMMCPConnection.m83
-rw-r--r--Source/MainController.m10
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"];