diff options
5 files changed, 78 insertions, 1 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 5772eb72..ee7f6039 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -39,7 +39,6 @@ #import "Locking.h" #import "Conversion.h" - @interface SPMySQLConnection (PrivateAPI) - (MYSQL *)_makeRawMySQLConnectionWithEncoding:(NSString *)encodingName isMasterConnection:(BOOL)isMaster; @@ -47,6 +46,8 @@ - (void)_updateConnectionVariables; - (void)_restoreConnectionVariables; - (BOOL)_checkConnectionIfNecessary; +- (void)_validateThreadSetup; ++ (void)_removeThreadVariables:(NSNotification *)aNotification; @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index 3ce0c0cd..3201b55f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -187,6 +187,9 @@ void _backgroundPingTask(void *ptr) // Set up a cleanup routine pthread_cleanup_push(_pingThreadCleanup, pingDetails); + // Initialise MySQL variables and handling on this thread + mysql_thread_init(); + // Set up a signal handler for SIGUSR1, to handle forced timeouts. signal(SIGUSR1, _forceThreadExit); @@ -209,6 +212,9 @@ void _pingThreadCleanup(void *pingDetails) { SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails; *(pingDetailsStruct->keepAlivePingActivePointer) = NO; + + // Clean up MySQL variables and handlers + mysql_thread_end(); } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 5df71e96..46615cae 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -76,6 +76,9 @@ } if (![self _checkConnectionIfNecessary]) return nil; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves // nul characters correctly. NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; @@ -229,6 +232,9 @@ return nil; } + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Check the connection if necessary, returning nil if the query couldn't be validated if (![self _checkConnectionIfNecessary]) return nil; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index f695d977..1022ccd1 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -137,6 +137,9 @@ // Lock the connection before using it [self _lockConnection]; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + // Get the process list MYSQL_RES *mysqlResult = mysql_list_processes(mySQLConnection); diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index 800157ca..d42b82e6 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -36,6 +36,9 @@ #include <pthread.h> #include <SystemConfiguration/SCNetworkReachability.h> +// Thread flag constant +static pthread_key_t mySQLThreadInitFlagKey; +static void *mySQLThreadFlag; #pragma mark Class constants @@ -76,6 +79,24 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS #pragma mark Initialisation and teardown /** + * In the one-off class initialisation, set up MySQL as necessary + */ ++ (void)initialize +{ + + // Set up a pthread thread-specific data key to be used across all classes and threads + pthread_key_create(&mySQLThreadInitFlagKey, NULL); + mySQLThreadFlag = malloc(1); + + // MySQL requires mysql_library_init() to be called before any other MySQL + // functions are used; although mysql_init() will call it automatically, it + // won't do so in a thread-safe manner, so setting it up first is safer. + // No arguments are required. + // Note that this will install MySQL's SIGPIPE handler. + mysql_library_init(0, NULL, NULL); +} + +/** * Initialise the SPMySQLConnection object, setting up class defaults. * * Typically initialisation would be followed by setting the connection details @@ -631,6 +652,13 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS MYSQL *theConnection = mysql_init(NULL); if (!theConnection) return NULL; + // Calling mysql_init will have automatically installed per-thread variables if necessary, + // so track their installation for removal and to avoid recreating again. + if (!pthread_getspecific(mySQLThreadInitFlagKey)) { + pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); + [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; + } + // Disable automatic reconnection, as it's handled in-framework to preserve // options, encodings and connection state. my_bool falseMyBool = FALSE; @@ -835,4 +863,37 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // Otherwise check the connection return [self checkConnection]; } + +/** + * Ensure that the thread this method is called on has been registered for + * use with MySQL. MySQL requires thread-specific variables for safe + * execution. + */ +- (void)_validateThreadSetup +{ + + // Check to see whether the handler has already been installed + if (pthread_getspecific(mySQLThreadInitFlagKey)) return; + + // If not, install it + mysql_thread_init(); + + // Mark the thread to avoid multiple installs + pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); + + // Set up the notification handler to deregister it + [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; +} + +/** + * Remove the MySQL variables and handlers from each closing thread which + * has had them installed to avoid memory leaks. + * This is a class method for easy global tracking; it will be called on the appropriate + * thread automatically. + */ ++ (void)_removeThreadVariables:(NSNotification *)aNotification +{ + mysql_thread_end(); +} + @end |