aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2012-03-14 01:16:18 +0000
committerrowanbeentje <rowan@beent.je>2012-03-14 01:16:18 +0000
commit79eff5bf42154da8d7730e0e0159160f68ec4e16 (patch)
tree38db570f7c36fbe2995774fefa627f1b467a9371 /Frameworks
parentd5e20720cf7f991a691d9a03e7f895211b7c98ad (diff)
downloadsequelpro-79eff5bf42154da8d7730e0e0159160f68ec4e16.tar.gz
sequelpro-79eff5bf42154da8d7730e0e0159160f68ec4e16.tar.bz2
sequelpro-79eff5bf42154da8d7730e0e0159160f68ec4e16.zip
Final feature work on the SPMySQL branch before merging:
- Add a ping keepalive managing object to prevent retain cycles from the NSTimer - Add -[SPMySQLConnection copy] support - Refactor Hans-Jörg Bibiko's database structure retrieval, moving it out of the MySQL framework and building it around a copy of the connection. This reduces the amount of connections-over-time used by Sequel Pro to two constant connections (addressing Issue #1097) and improves robustness. - Use the database structure retrieval connection for faster query cancellation without an extra connection required, if possible
Diffstat (limited to 'Frameworks')
-rw-r--r--Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj16
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h36
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m71
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h5
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m15
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h1
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m10
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h6
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m16
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h45
-rw-r--r--Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m127
11 files changed, 311 insertions, 37 deletions
diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj
index 933a68b7..2f8d300c 100644
--- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj
+++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj
@@ -28,6 +28,10 @@
584294FB14CB8002000F8438 /* Encoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294F914CB8002000F8438 /* Encoding.m */; };
584294FE14CB8002000F8438 /* Server Info.h in Headers */ = {isa = PBXBuildFile; fileRef = 584294FC14CB8002000F8438 /* Server Info.h */; settings = {ATTRIBUTES = (Public, ); }; };
584294FF14CB8002000F8438 /* Server Info.m in Sources */ = {isa = PBXBuildFile; fileRef = 584294FD14CB8002000F8438 /* Server Info.m */; };
+ 584D812E15057ECD00F24774 /* SPMySQLKeepAliveTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */; };
+ 584D812F15057ECD00F24774 /* SPMySQLKeepAliveTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */; };
+ 584D82551509775000F24774 /* Copying.h in Headers */ = {isa = PBXBuildFile; fileRef = 584D82531509775000F24774 /* Copying.h */; };
+ 584D82561509775000F24774 /* Copying.m in Sources */ = {isa = PBXBuildFile; fileRef = 584D82541509775000F24774 /* Copying.m */; };
586A99FB14F02E21007F82BF /* SPMySQLStreamingResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 586A99F914F02E21007F82BF /* SPMySQLStreamingResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
586A99FC14F02E21007F82BF /* SPMySQLStreamingResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */; };
586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */ = {isa = PBXBuildFile; fileRef = 586AA16514F30C5F007F82BF /* Convenience Methods.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -92,6 +96,10 @@
584294F914CB8002000F8438 /* Encoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Encoding.m; path = "Source/SPMySQLConnection Categories/Encoding.m"; sourceTree = "<group>"; };
584294FC14CB8002000F8438 /* Server Info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Server Info.h"; path = "Source/SPMySQLConnection Categories/Server Info.h"; sourceTree = "<group>"; };
584294FD14CB8002000F8438 /* Server Info.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Server Info.m"; path = "Source/SPMySQLConnection Categories/Server Info.m"; sourceTree = "<group>"; };
+ 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLKeepAliveTimer.h; path = Source/SPMySQLKeepAliveTimer.h; sourceTree = "<group>"; };
+ 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLKeepAliveTimer.m; path = Source/SPMySQLKeepAliveTimer.m; sourceTree = "<group>"; };
+ 584D82531509775000F24774 /* Copying.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Copying.h; path = "Source/SPMySQLConnection Categories/Copying.h"; sourceTree = "<group>"; };
+ 584D82541509775000F24774 /* Copying.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Copying.m; path = "Source/SPMySQLConnection Categories/Copying.m"; sourceTree = "<group>"; };
586A99F914F02E21007F82BF /* SPMySQLStreamingResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResult.h; path = Source/SPMySQLStreamingResult.h; sourceTree = "<group>"; };
586A99FA14F02E21007F82BF /* SPMySQLStreamingResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLStreamingResult.m; path = Source/SPMySQLStreamingResult.m; sourceTree = "<group>"; };
586AA0E714F1CEC8007F82BF /* Readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Readme.txt; sourceTree = "<group>"; };
@@ -202,6 +210,8 @@
58C7C1E314DB6E4C00436315 /* SPMySQLFastStreamingResult.m */,
58C7C1E114DB6E3000436315 /* Result Categories */,
580A331B14D75CCF000D6933 /* Result types */,
+ 584D812C15057ECD00F24774 /* SPMySQLKeepAliveTimer.h */,
+ 584D812D15057ECD00F24774 /* SPMySQLKeepAliveTimer.m */,
);
name = Classes;
sourceTree = "<group>";
@@ -288,6 +298,8 @@
584294EB14CB8002000F8438 /* Connection Categories */ = {
isa = PBXGroup;
children = (
+ 584D82531509775000F24774 /* Copying.h */,
+ 584D82541509775000F24774 /* Copying.m */,
58C00AB314E4892E00AC489A /* Delegate & Proxy.h */,
58C00AB414E4892E00AC489A /* Delegate & Proxy.m */,
58C00BCF14E7459600AC489A /* Databases & Tables.h */,
@@ -387,6 +399,8 @@
586A99FB14F02E21007F82BF /* SPMySQLStreamingResult.h in Headers */,
586AA16714F30C5F007F82BF /* Convenience Methods.h in Headers */,
586AA81414F6C648007F82BF /* SPMySQLArrayAdditions.h in Headers */,
+ 584D812E15057ECD00F24774 /* SPMySQLKeepAliveTimer.h in Headers */,
+ 584D82551509775000F24774 /* Copying.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -470,6 +484,8 @@
58C00BD214E7459600AC489A /* Databases & Tables.m in Sources */,
586A99FC14F02E21007F82BF /* SPMySQLStreamingResult.m in Sources */,
586AA16814F30C5F007F82BF /* Convenience Methods.m in Sources */,
+ 584D812F15057ECD00F24774 /* SPMySQLKeepAliveTimer.m in Sources */,
+ 584D82561509775000F24774 /* Copying.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h
new file mode 100644
index 00000000..83d055d5
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.h
@@ -0,0 +1,36 @@
+//
+// $Id$
+//
+// Copying.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on March 8, 2012
+// Copyright (c) 2012 Rowan Beentje. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+
+@interface SPMySQLConnection (Copying) <NSCopying>
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m
new file mode 100644
index 00000000..c2df2a4b
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Copying.m
@@ -0,0 +1,71 @@
+//
+// $Id$
+//
+// Copying.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on March 8, 2012
+// Copyright (c) 2012 Rowan Beentje. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+#import "Copying.h"
+
+@implementation SPMySQLConnection (Copying)
+
+/**
+ * Provide a copy of the SPMySQLConnection instance.
+ * The copy should inherit the full setup, but will not inherit
+ * the connection state - it will not be connected, and any connection
+ * details such as the selected database/encoding will not be inherited.
+ * Note that any proxy will not be referenced in the new connection, and
+ * should also be set if desired.
+ */
+- (id)copyWithZone:(NSZone *)zone
+{
+ SPMySQLConnection *copy = [[[self class] allocWithZone:zone] init];
+
+ // Synthesized details
+ [copy setDelegate:delegate];
+ [copy setHost:host];
+ [copy setUsername:username];
+ [copy setPassword:password];
+ [copy setPort:port];
+ [copy setUseSocket:useSocket];
+ [copy setSocketPath:socketPath];
+ [copy setUseSSL:useSSL];
+ [copy setSslKeyFilePath:sslKeyFilePath];
+ [copy setSslCertificatePath:sslCertificatePath];
+ [copy setSslCACertificatePath:sslCACertificatePath];
+ [copy setTimeout:timeout];
+ [copy setUseKeepAlive:useKeepAlive];
+ [copy setRetryQueriesOnConnectionFailure:retryQueriesOnConnectionFailure];
+ [copy setDelegateQueryLogging:delegateQueryLogging];
+
+ // Active connection state details, like selected database and encoding, are *not* copied.
+
+ return copy;
+}
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
index 3788c653..e84c4ca6 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h
@@ -40,11 +40,8 @@ typedef struct {
@interface SPMySQLConnection (Ping_and_KeepAlive)
-// Setup functions
-- (void)_initKeepAlivePingTimer;
-
// Keepalive ping initialisation
-- (void)_keepAlive:(NSTimer *)theTimer;
+- (void)_keepAlive;
- (void)_threadedKeepAlive;
// Master ping method
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
index 9e25edcb..3ce0c0cd 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m
@@ -38,19 +38,6 @@
@implementation SPMySQLConnection (Ping_and_KeepAlive)
#pragma mark -
-#pragma mark Setup functions
-
-/**
- * Set up the keepalive timer; this should be called on the main
- * thread, to ensure the timer isn't descheduled when child threads
- * terminate.
- */
-- (void)_initKeepAlivePingTimer
-{
- keepAliveTimer = [[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(_keepAlive:) userInfo:nil repeats:YES] retain];
-}
-
-#pragma mark -
#pragma mark Keepalive ping initialisation
/**
@@ -58,7 +45,7 @@
* This method is called every ten seconds and spawns a thread which determines
* whether or not it should perform a ping.
*/
-- (void)_keepAlive:(NSTimer *)theTimer
+- (void)_keepAlive
{
// Do nothing if not connected or if keepalive is disabled
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h
index 9ab8bc6a..0f086a89 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.h
@@ -61,7 +61,6 @@
// Query cancellation
- (void)cancelCurrentQuery;
-- (BOOL)lastQueryWasCancelled;
- (BOOL)lastQueryWasCancelledUsingReconnect;
@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
index cb5ce70d..9b54029c 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m
@@ -282,7 +282,7 @@
} else {
// Prevent retries if the query was cancelled or not a connection error
- if (lastQueryWasCancelled && ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) {
+ if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) {
break;
}
}
@@ -549,14 +549,6 @@
}
/**
- * Returns whether the last query was cancelled using cancelCurrentQuery.
- */
-- (BOOL)lastQueryWasCancelled
-{
- return lastQueryWasCancelled;
-}
-
-/**
* If the last query was cancelled, returns whether that query cancellation
* required the connection to be reset or whether the query was successfully
* cancelled leaving the connection intact.
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
index 5d1a5d11..8f3b7f9f 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h
@@ -30,6 +30,8 @@
//
// More info at <http://code.google.com/p/sequel-pro/>
+@class SPMySQLKeepAliveTimer;
+
@interface SPMySQLConnection : NSObject {
// Delegate
@@ -80,7 +82,7 @@
// Timeout and keep-alive
NSUInteger timeout;
BOOL useKeepAlive;
- NSTimer *keepAliveTimer;
+ SPMySQLKeepAliveTimer *keepAliveTimer;
CGFloat keepAliveInterval;
uint64_t lastKeepAliveTime;
NSUInteger keepAlivePingFailures;
@@ -153,6 +155,8 @@
@property (readwrite, assign) BOOL delegateQueryLogging;
+@property (readwrite, assign) BOOL lastQueryWasCancelled;
+
#pragma mark -
#pragma mark Connection and disconnection
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
index 4968266d..4f1e8a74 100644
--- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m
@@ -31,6 +31,7 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import "SPMySQL Private APIs.h"
+#import "SPMySQLKeepAliveTimer.h"
#include <mach/mach_time.h>
#include <pthread.h>
#include <SystemConfiguration/SCNetworkReachability.h>
@@ -71,6 +72,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
@synthesize mysqlConnectionThreadId;
@synthesize retryQueriesOnConnectionFailure;
@synthesize delegateQueryLogging;
+@synthesize lastQueryWasCancelled;
#pragma mark -
#pragma mark Initialisation and teardown
@@ -161,11 +163,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
retryQueriesOnConnectionFailure = YES;
// Start the ping keepalive timer
- if ([NSThread isMainThread]) {
- [self _initKeepAlivePingTimer];
- } else {
- [self performSelectorOnMainThread:@selector(_initKeepAlivePingTimer) withObject:nil waitUntilDone:YES];
- }
+ keepAliveTimer = [[SPMySQLKeepAliveTimer alloc] initWithInterval:10 target:self selector:@selector(_keepAlive)];
}
return self;
@@ -181,6 +179,10 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
// Unset the delegate
[self setDelegate:nil];
+ // Clear the keepalive timer
+ [keepAliveTimer invalidate];
+ [keepAliveTimer release];
+
// Disconnect if appropriate (which should also disconnect any proxy)
[self disconnect];
@@ -196,14 +198,12 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS
}
[connectionLock release], connectionLock = nil;
- [encoding dealloc];
+ [encoding release];
if (previousEncoding) [previousEncoding release], previousEncoding = nil;
if (database) [database release], database = nil;
if (serverVersionString) [serverVersionString release], serverVersionString = nil;
if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil;
- [keepAliveTimer invalidate];
- [keepAliveTimer release];
[delegateDecisionLock release];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h
new file mode 100644
index 00000000..ebe46bed
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.h
@@ -0,0 +1,45 @@
+//
+// $Id$
+//
+// SPMySQLKeepAliveTimer.h
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on March 5, 2012
+// Copyright (c) 2012 Rowan Beentje. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+
+@interface SPMySQLKeepAliveTimer : NSObject {
+ id timerTarget;
+ SEL timerSelector;
+ NSTimeInterval timerRepeatInterval;
+
+ NSTimer *wrappedTimer;
+}
+
+- (id)initWithInterval:(NSTimeInterval)anInterval target:(id)aTarget selector:(SEL)aSelector;
+- (void)invalidate;
+
+@end
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m
new file mode 100644
index 00000000..f9164aff
--- /dev/null
+++ b/Frameworks/SPMySQLFramework/Source/SPMySQLKeepAliveTimer.m
@@ -0,0 +1,127 @@
+//
+// $Id$
+//
+// SPMySQLKeepAliveTimer.m
+// SPMySQLFramework
+//
+// Created by Rowan Beentje (rowan.beent.je) on March 5, 2012
+// Copyright (c) 2012 Rowan Beentje. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// More info at <http://code.google.com/p/sequel-pro/>
+
+
+#import "SPMySQLKeepAliveTimer.h"
+#import "SPMySQL Private APIs.h"
+
+@interface SPMySQLKeepAliveTimer (Private_API)
+
+- (void)_initKeepAliveTimer;
+- (void)_forwardPing;
+
+@end
+
+#pragma mark -
+
+@implementation SPMySQLKeepAliveTimer
+
+/**
+ * Prevent SPMySQLKeepAliveTimer from being init'd normally.
+ */
+- (id)init
+{
+ [NSException raise:NSInternalInconsistencyException format:@"SPMySQLKeepAliveTimers should not be init'd directly; use initWithInterval:target:selector: instead."];
+ return nil;
+}
+
+/**
+ * Initialise the SPMySQLKeepAliveTimer. This also sets up the contained timer,
+ * which has to be wrapped in this class to prevent retain cycles preventing the
+ * parent connection from being released.
+ *
+ * After initialisation, the delegate should be set to ensure that the timer events
+ * are received.
+ */
+- (id)initWithInterval:(NSTimeInterval)anInterval target:(id)aTarget selector:(SEL)aSelector
+{
+ if ((self = [super init])) {
+ wrappedTimer = nil;
+
+ // Keep a weak reference to the target
+ timerTarget = aTarget;
+ timerSelector = aSelector;
+ timerRepeatInterval = anInterval;
+
+ // Ensure the timer is set up on the main thread
+ if ([NSThread isMainThread]) {
+ [self _initKeepAliveTimer];
+ } else {
+ [self performSelectorOnMainThread:@selector(_initKeepAliveTimer) withObject:nil waitUntilDone:YES];
+ }
+ }
+
+ return self;
+}
+
+/**
+ * Invalidate the wrapped timer, which also releases the reference to the timer
+ * target (this object), breaking retain loops.
+ */
+- (void)invalidate
+{
+ if ([NSThread isMainThread]) {
+ [wrappedTimer invalidate];
+ } else {
+ [wrappedTimer performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
+ }
+}
+
+- (void)dealloc
+{
+ [wrappedTimer dealloc];
+ [super dealloc];
+}
+
+@end
+
+@implementation SPMySQLKeepAliveTimer (Private_API)
+
+/**
+ * Set up the timer to tickle the target. This must be set up on the main thread
+ * to ensure the timer events keep firing.
+ */
+- (void)_initKeepAliveTimer
+{
+ wrappedTimer = [[NSTimer scheduledTimerWithTimeInterval:timerRepeatInterval target:self selector:@selector(_forwardPing) userInfo:nil repeats:YES] retain];
+}
+
+/**
+ * Forward the NSTimer-fired ping to the target object. Performing this forwarding
+ * breaks the retain cycle.
+ */
+- (void)_forwardPing
+{
+ [timerTarget performSelector:timerSelector];
+}
+
+@end