aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m
diff options
context:
space:
mode:
Diffstat (limited to 'Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m')
-rw-r--r--Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m298
1 files changed, 134 insertions, 164 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m
index 3dab324d..2008a3aa 100644
--- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m
+++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m
@@ -41,13 +41,6 @@
#include <mach/mach_time.h>
#include <SystemConfiguration/SystemConfiguration.h>
-BOOL lastPingSuccess;
-BOOL pingActive;
-NSInteger pingFailureCount;
-BOOL keepAliveActive;
-
-static void forceThreadExit(int signalNumber);
-
const NSUInteger kMCPConnectionDefaultOption = CLIENT_COMPRESS | CLIENT_REMEMBER_OPTIONS | CLIENT_MULTI_RESULTS;
const char *kMCPConnectionDefaultSocket = MYSQL_UNIX_ADDR;
const char *kMCPSSLCipherList = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RSA-AES128-SHA:AES128-SHA:AES256-RMD:AES128-RMD:DES-CBC3-RMD:DHE-RSA-AES256-RMD:DHE-RSA-AES128-RMD:DHE-RSA-DES-CBC3-RMD:RC4-SHA:RC4-MD5:DES-CBC3-SHA:DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC-SHA";
@@ -118,7 +111,6 @@ static BOOL sTruncateLongFieldInLogs = YES;
sslKeyFilePath = nil;
sslCertificatePath = nil;
sslCACertificatePath = nil;
- keepAliveThread = NULL;
lastKeepAliveTime = 0;
pingThread = NULL;
connectionProxy = nil;
@@ -132,6 +124,9 @@ static BOOL sTruncateLongFieldInLogs = YES;
isDisconnecting = NO;
userTriggeredDisconnect = NO;
automaticReconnectAttempts = 0;
+ lastPingSuccess = NO;
+ lastPingBlocked = NO;
+ pingThreadActive = NO;
pingFailureCount = 0;
// Initialize ivar defaults
@@ -696,7 +691,7 @@ static BOOL sTruncateLongFieldInLogs = YES;
BOOL connectionVerified = FALSE;
// Check whether the connection is still operational via a wrapped version of MySQL ping.
- connectionVerified = [self pingConnection];
+ connectionVerified = [self pingConnectionUsingLoopDelay:400];
// If the connection doesn't appear to be responding, and we can still attempt an automatic
// reconnect (only once each connection - eg an automatic reconnect failure prevents loops,
@@ -751,58 +746,155 @@ static BOOL sTruncateLongFieldInLogs = YES;
}
/**
+ * Restore the connection encoding details as necessary based on the delegate-provided
+ * details.
+ */
+- (void)restoreConnectionDetails
+{
+ connectionThreadId = mConnection->thread_id;
+ connectionStartTime = mach_absolute_time();
+ [self fetchMaxAllowedPacket];
+
+ [self setEncoding:encoding];
+ [self setEncodingUsesLatin1Transport:encodingUsesLatin1Transport];
+}
+
+/**
+ * Allow controlling over whether queries are allowed to retry after a connection failure.
+ * This defaults to YES on init, and is intended to allow temporary disabling in situations
+ * where the query result is checked and displayed to the user without any repurcussions on
+ * failure.
+ */
+- (void)setAllowQueryRetries:(BOOL)allow
+{
+ retryAllowed = allow;
+}
+
+/**
+ * Retrieve the time elapsed since the connection was established, in seconds.
+ * This time is retrieved in a monotonically increasing fashion and is high
+ * precision; it is used internally for query timing, and is reset on reconnections.
+ */
+- (double)timeConnected
+{
+ if (connectionStartTime == -1) return -1;
+
+ uint64_t currentTime_t = mach_absolute_time() - connectionStartTime;
+ Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
+
+ return (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-9);
+}
+
+#pragma mark -
+#pragma mark Pinging and keepalive
+
+/**
* This function provides a method of pinging the remote server while also enforcing
* the specified connection time. This is required because low-level net reads can
* block indefinitely if the remote server disappears or on network issues - setting
* the MYSQL_OPT_READ_TIMEOUT (and the WRITE equivalent) would "fix" ping, but cause
* long queries to be terminated.
+ * The supplied loop delay number controls how tight the thread checking loop is, in
+ * microseconds, to allow differentiating foreground and background pings.
* Unlike mysql_ping, this function returns FALSE on failure and TRUE on success.
*/
-- (BOOL)pingConnection
+- (BOOL)pingConnectionUsingLoopDelay:(NSUInteger)loopDelay
{
+ if (!mConnected) return NO;
+
+ uint64_t pingStartTime_t, currentTime_t;
+ Nanoseconds elapsedNanoseconds;
+ BOOL threadCancelled = NO;
+
// Set up a query lock
[self lockConnection];
- uint64_t currentTime_t;
- Nanoseconds elapsedTime;
- uint64_t pingStartTime_t = mach_absolute_time();
- lastPingSuccess = FALSE;
- pingActive = YES;
+ lastPingSuccess = NO;
+ lastPingBlocked = NO;
+ pingThreadActive = YES;
+
+ // Use a ping timeout defaulting to thirty seconds, but using the connection timeout if set
+ NSInteger pingTimeout = 30;
+ if (connectionTimeout > 0) pingTimeout = connectionTimeout;
+
+ // Set up a struct containing details the ping task will need
+ MCPConnectionPingDetails pingDetails;
+ pingDetails.mySQLConnection = mConnection;
+ pingDetails.lastPingSuccessPointer = &lastPingSuccess;
+ pingDetails.pingActivePointer = &pingThreadActive;
- // Create a pthread for the ping, so we can force it to end after the connection timeout
+ // Create a pthread for the ping
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&pingThread, NULL, (void *)&pingConnectionTask, (void *)mConnection);
+ pthread_create(&pingThread, &attr, (void *)&backgroundPingTask, &pingDetails);
- // Loop tightly until the ping responds, or the elapsed time exceeds the connection timeout
- while (pingActive) {
- currentTime_t = mach_absolute_time() - pingStartTime_t;
- elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
- if (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-9 > connectionTimeout) break;
- usleep(400);
- }
+ // Record the ping start time
+ pingStartTime_t = mach_absolute_time();
- // If the connection timed out, kill the thread and set status to failed
- if (pingActive) {
- pthread_cancel(pingThread);
- lastPingSuccess = FALSE;
- }
+ // Loop until the ping completes
+ do {
+ usleep(loopDelay);
+
+ // If the ping timeout has been exceeded, force a timeout; double-check that the
+ // thread is still active.
+ currentTime_t = mach_absolute_time() - pingStartTime_t ;
+ elapsedNanoseconds = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
+ if (((double)UnsignedWideToUInt64(elapsedNanoseconds)) * 1e-9 > pingTimeout && pingThreadActive && !threadCancelled) {
+ pthread_cancel(pingThread);
+ threadCancelled = YES;
+
+ // If the timeout has been exceeded by an additional two seconds, and the thread is
+ // still active, kill the thread. This can occur in certain network conditions causing
+ // a blocking read.
+ } else if (((double)UnsignedWideToUInt64(elapsedNanoseconds)) * 1e-9 > (pingTimeout + 2) && pingThreadActive) {
+ pthread_kill(pingThread, SIGUSR1);
+ pingThreadActive = NO;
+ lastPingBlocked = YES;
+ }
+ } while (pingThreadActive);
+
+ // Clean up
+ pingThread = NULL;
pthread_attr_destroy(&attr);
+ // Unlock the connection
[self unlockConnection];
return lastPingSuccess;
}
/**
- * This function is paired with pingConnection, and performs the checking ping in a pthread,
- * allowing the thread to be cancelled if it does not respond.
+ * Actually perform a keepalive ping - intended for use within a pthread.
+ */
+void backgroundPingTask(void *ptr)
+{
+ MCPConnectionPingDetails *pingDetails = (MCPConnectionPingDetails *)ptr;
+
+ // Set up a cleanup routine
+ pthread_cleanup_push(pingThreadCleanup, pingDetails);
+
+ // Set up a signal handler for SIGUSR1, to handle forced timeouts.
+ signal(SIGUSR1, forceThreadExit);
+
+ // Perform a ping
+ *(pingDetails->lastPingSuccessPointer) = (BOOL)(!mysql_ping(pingDetails->mySQLConnection));
+
+ // Call the cleanup routine
+ pthread_cleanup_pop(1);
+}
+
+/**
+ * Support forcing a thread to exit as a result of a signal.
*/
-void pingConnectionTask(void *ptr)
+void forceThreadExit(int signalNumber)
+{
+ pthread_exit(NULL);
+}
+
+void pingThreadCleanup(MCPConnectionPingDetails *pingDetails)
{
- lastPingSuccess = (BOOL)(!mysql_ping((MYSQL *)ptr));
- pingActive = NO;
+ *(pingDetails->pingActivePointer) = NO;
}
/**
@@ -827,9 +919,10 @@ void pingConnectionTask(void *ptr)
return;
}
- // Attempt to lock the connection. If the connection currently is busy,
+ // Attempt to lock the connection. If the connection is currently busy,
// we don't need a ping.
if (![self tryLockConnection]) return;
+ [self unlockConnection];
// Store the ping time
lastKeepAliveTime = timeConnected;
@@ -844,145 +937,22 @@ void pingConnectionTask(void *ptr)
*/
- (void)threadedKeepAlive
{
- uint64_t currentTime_t;
- Nanoseconds currentNanoseconds;
- double pingStartTime;
- BOOL threadCancelled = NO;
-
- if (!mConnected || keepAliveThread != NULL) {
- // unlock the connection now. it has been locked in keepAlive:
- [self unlockConnection];
- return;
- }
// If the maximum number of ping failures has been reached, trigger a reconnect
- if (pingFailureCount >= 3) {
+ if (lastPingBlocked || pingFailureCount >= 3) {
NSAutoreleasePool *connectionPool = [[NSAutoreleasePool alloc] init];
- [self unlockConnection];
[self reconnect];
[connectionPool drain];
return;
}
- // Use a ping timeout between zero and thirty seconds
- NSInteger pingTimeout = 30;
- if (connectionTimeout > 0 && connectionTimeout < pingTimeout) pingTimeout = connectionTimeout;
-
- // Create a pthread for the actual keepalive
- keepAliveActive = YES;
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&keepAliveThread, &attr, (void *)&performThreadedKeepAlive, (void *)mConnection);
-
- // Record the ping start time
- currentTime_t = mach_absolute_time() - connectionStartTime;
- currentNanoseconds = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
- pingStartTime = UnsignedWideToUInt64(currentNanoseconds);
-
- // Loop until the ping completes
- do {
- usleep(10000);
-
- // If the ping timeout has been exceeded, force a timeout; double-check that keepAliveActive
- // is still set to NO, as otherwise the thread id may have already been reused causing
- // cancellation of the incorrect thread.
- currentTime_t = mach_absolute_time() - connectionStartTime;
- currentNanoseconds = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
- if ((UnsignedWideToUInt64(currentNanoseconds) - pingStartTime) * 1e-9 > pingTimeout && keepAliveActive && !threadCancelled) {
- pthread_cancel(keepAliveThread);
- threadCancelled = YES;
-
- // If the timeout has been exceeded by an additional two seconds, kill the thread.
- } else if ((UnsignedWideToUInt64(currentNanoseconds) - pingStartTime) * 1e-9 > (pingTimeout + 2) && keepAliveActive) {
- pthread_kill(keepAliveThread, SIGUSR1);
- keepAliveActive = NO;
- }
- } while (keepAliveActive);
-
- // Unlock the connection now, locked in keepAlive: at the start of the ping
- [self unlockConnection];
-
- // Clean up
- keepAliveThread = NULL;
- pthread_attr_destroy(&attr);
-}
-
-/**
- * Actually perform a keepalive ping - intended for use within a pthread.
- */
-void performThreadedKeepAlive(void *ptr)
-{
-
- // Set up a signal handler for SIGUSR1, to handle forced timeouts.
- struct sigaction timeoutAction;
- timeoutAction.sa_handler = forceThreadExit;
- sigemptyset(&timeoutAction.sa_mask);
- timeoutAction.sa_flags = 0;
- sigaction(SIGUSR1, &timeoutAction, NULL);
-
- if (mysql_ping((MYSQL *)ptr)) {
- pingFailureCount++;
- } else {
+ // Otherwise, perform a background ping.
+ BOOL pingResult = [self pingConnectionUsingLoopDelay:10000];
+ if (pingResult) {
pingFailureCount = 0;
+ } else {
+ pingFailureCount++;
}
- keepAliveActive = NO;
-
-
- // Reset and clear the SIGUSR1 used to check connection timeouts.
- timeoutAction.sa_handler = SIG_IGN;
- sigemptyset(&timeoutAction.sa_mask);
- timeoutAction.sa_flags = 0;
- sigaction(SIGUSR1, &timeoutAction, NULL);
-}
-
-/**
- * Support forcing a thread to exit as a result of a signal.
- */
-static void forceThreadExit(int signalNumber)
-{
- pthread_exit(NULL);
-}
-
-
-/**
- * Restore the connection encoding details as necessary based on the delegate-provided
- * details.
- */
-- (void)restoreConnectionDetails
-{
- connectionThreadId = mConnection->thread_id;
- connectionStartTime = mach_absolute_time();
- [self fetchMaxAllowedPacket];
-
- [self setEncoding:encoding];
- [self setEncodingUsesLatin1Transport:encodingUsesLatin1Transport];
-}
-
-/**
- * Allow controlling over whether queries are allowed to retry after a connection failure.
- * This defaults to YES on init, and is intended to allow temporary disabling in situations
- * where the query result is checked and displayed to the user without any repurcussions on
- * failure.
- */
-- (void)setAllowQueryRetries:(BOOL)allow
-{
- retryAllowed = allow;
-}
-
-/**
- * Retrieve the time elapsed since the connection was established, in seconds.
- * This time is retrieved in a monotonically increasing fashion and is high
- * precision; it is used internally for query timing, and is reset on reconnections.
- */
-- (double)timeConnected
-{
- if (connectionStartTime == -1) return -1;
-
- uint64_t currentTime_t = mach_absolute_time() - connectionStartTime;
- Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t));
-
- return (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-9);
}
#pragma mark -