From e3ac547a6fb5b73581e61bb35ac2f3f7fca67a96 Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Fri, 11 Feb 2011 02:06:18 +0000 Subject: - Revert r3187, which only masked an underlying bug and caused double columns in views - In SPTableStructureDelete, don't request the encoding if the table is reloading; this prevents multithreaded data fetches and race conditions causing Issue #974. - Alter SPTableData to use thread mutexes instead of a boolean to prevent threading issues, which also fixes Issue #974 in a different way. Alter race condition checks to block their threads instead of returning bogus information to prevent state issues. - Alter table loading to no longer load trigger information until required, speeding up use of other views and cleaning up the console log. --- Source/SPDatabaseViewController.m | 25 ++++----- Source/SPTableData.h | 4 +- Source/SPTableData.m | 110 ++++++++++++++++++-------------------- Source/SPTableStructureDelegate.m | 5 +- Source/SPTableTriggers.h | 1 + Source/SPTableTriggers.m | 32 +++++++---- 6 files changed, 90 insertions(+), 87 deletions(-) diff --git a/Source/SPDatabaseViewController.m b/Source/SPDatabaseViewController.m index 03153230..a6600d3f 100644 --- a/Source/SPDatabaseViewController.m +++ b/Source/SPDatabaseViewController.m @@ -280,7 +280,7 @@ [tableSourceInstance loadTable:nil]; [tableContentInstance loadTable:nil]; [[extendedTableInfoInstance onMainThread] loadTable:nil]; - [[tableTriggersInstance onMainThread] loadTriggers]; + [[tableTriggersInstance onMainThread] resetInterface]; structureLoaded = NO; contentLoaded = NO; statusLoaded = NO; @@ -393,8 +393,12 @@ // Update the window title [self updateWindowTitle:self]; - // Reset table information caches + // Reset table information caches and mark that all loaded views require their data reloading [tableDataInstance resetAllData]; + structureLoaded = NO; + contentLoaded = NO; + statusLoaded = NO; + triggersLoaded = NO; // Ensure status and details are fetched using UTF8 NSString *previousEncoding = [mySQLConnection encoding]; @@ -408,7 +412,8 @@ [tableDataInstance updateStatusInformationForCurrentTable]; // Check the current encoding against the table encoding to see whether - // an encoding change and reset is required + // an encoding change and reset is required. This also caches table information on + // the working thread. if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { // tableEncoding == nil indicates that there was an error while retrieving table data @@ -426,24 +431,12 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - // Cache table information on the working thread - if (selectedTableType == SPTableTypeView) - [tableDataInstance updateInformationForCurrentView]; - else - [tableDataInstance updateInformationForCurrentTable]; - // Notify listeners of the table change now that the state is fully set up. [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:self]; // Restore view states as appropriate [spHistoryControllerInstance restoreViewStates]; - // Mark that all loaded views require their data reloading - structureLoaded = NO; - contentLoaded = NO; - statusLoaded = NO; - triggersLoaded = NO; - // Load the currently selected view if looking at a table or view if (tableEncoding && (selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable)) { @@ -475,7 +468,7 @@ if (!structureLoaded) [tableSourceInstance loadTable:nil]; if (!contentLoaded) [tableContentInstance loadTable:nil]; if (!statusLoaded) [[extendedTableInfoInstance onMainThread] loadTable:nil]; - if (!triggersLoaded) [[tableTriggersInstance onMainThread] loadTriggers]; + if (!triggersLoaded) [[tableTriggersInstance onMainThread] resetInterface]; // Update the "Show Create Syntax" window if it's already opened // according to the selected table/view/proc/func diff --git a/Source/SPTableData.h b/Source/SPTableData.h index cb24d51f..4503bd03 100644 --- a/Source/SPTableData.h +++ b/Source/SPTableData.h @@ -41,9 +41,9 @@ MCPConnection *mySQLConnection; - BOOL isWorking; - BOOL tableHasAutoIncrementField; + pthread_mutex_t dataProcessingLock; + BOOL tableHasAutoIncrementField; } @property (readwrite, assign) BOOL isWorking; diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 28717864..c63d10ff 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -31,11 +31,13 @@ #import "RegexKitLite.h" #import "SPServerSupport.h" +@interface SPTableData (PrivateAPI) +- (void)_loopWhileWorking; +@end + @implementation SPTableData -@synthesize isWorking; @synthesize tableHasAutoIncrementField; -; /** * Init class. @@ -52,9 +54,9 @@ tableEncoding = nil; tableCreateSyntax = nil; mySQLConnection = nil; - isWorking = NO; tableHasAutoIncrementField = NO; + pthread_mutex_init(&dataProcessingLock, NULL); } return self; @@ -79,8 +81,8 @@ - (NSString *) tableEncoding { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if (tableEncoding == nil) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -98,8 +100,8 @@ - (NSString *) tableCreateSyntax { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if (tableCreateSyntax == nil) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -123,8 +125,8 @@ - (NSArray *) columns { - // Return if CREATE SYNTAX is being parsed - if(isWorking) return [NSArray array]; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -149,9 +151,8 @@ */ - (NSArray *) triggers { - // Return if CREATE SYNTAX is being parsed - if (isWorking) return [NSArray array]; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; // If triggers is nil, the triggers need to be loaded - if a table is selected on MySQL >= 5.0.2 if (!triggers) { @@ -173,10 +174,8 @@ */ - (NSDictionary *) columnWithName:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -196,10 +195,8 @@ */ - (NSArray *) columnNames { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return [NSArray array]; - + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columnNames count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -219,9 +216,8 @@ */ - (NSDictionary *) columnAtIndex:(NSInteger)index { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -242,9 +238,8 @@ - (BOOL) columnIsBlobOrText:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return YES; // to be at the safe side + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -266,9 +261,8 @@ - (BOOL) columnIsGeometry:(NSString *)colName { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return YES; // to be at the safe side + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([columns count] == 0) { if ([tableListInstance tableType] == SPTableTypeView) { @@ -289,9 +283,8 @@ */ - (NSString *) statusValueForKey:(NSString *)aKey { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([status count] == 0) { [self updateStatusInformationForCurrentTable]; @@ -318,9 +311,8 @@ */ - (NSDictionary *) statusValues { - - // Return if CREATE SYNTAX is being parsed - if(isWorking) return nil; + // If processing is already in action, wait for it to complete + [self _loopWhileWorking]; if ([status count] == 0) { [self updateStatusInformationForCurrentTable]; @@ -380,7 +372,7 @@ */ - (BOOL) updateInformationForCurrentTable { - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); NSDictionary *tableData = nil; NSDictionary *columnData; @@ -396,7 +388,7 @@ } if (tableData == nil ) { - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -412,7 +404,7 @@ } tableEncoding = [[NSString alloc] initWithString:[tableData objectForKey:@"encoding"]]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -423,8 +415,7 @@ */ - (BOOL) updateInformationForCurrentView { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); NSDictionary *viewData = [self informationForView:[tableListInstance tableName]]; NSDictionary *columnData; @@ -436,7 +427,7 @@ [columns removeAllObjects]; [columnNames removeAllObjects]; [constraints removeAllObjects]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -452,7 +443,7 @@ } tableEncoding = [[NSString alloc] initWithString:[viewData objectForKey:@"encoding"]]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -476,10 +467,6 @@ unichar quoteCharacter; BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; - [columns removeAllObjects]; - [columnNames removeAllObjects]; - [constraints removeAllObjects]; - // Catch unselected tables and return nil if ([tableName isEqualToString:@""] || !tableName) return nil; @@ -925,14 +912,13 @@ */ - (BOOL)updateStatusInformationForCurrentTable { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; // Catch unselected tables and return false if ([[tableListInstance tableName] isEqualToString:@""] || ![tableListInstance tableName]) { - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -979,7 +965,7 @@ [mySQLConnection getLastErrorMessage]]); if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -997,7 +983,7 @@ if ([[status objectForKey:@"Engine"] isNSNull]) { [status setDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"Error", @"Engine", [NSString stringWithFormat:NSLocalizedString(@"An error occurred retrieving table information. MySQL said: %@", @"MySQL table info retrieval error message"), [status objectForKey:@"Comment"]], @"Comment", [tableListInstance tableName], @"Name", nil]]; if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return FALSE; } @@ -1032,7 +1018,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return TRUE; } @@ -1042,8 +1028,7 @@ */ - (BOOL) updateTriggersForCurrentTable { - - isWorking = YES; + pthread_mutex_lock(&dataProcessingLock); // Ensure queries are made in UTF8 BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; @@ -1067,7 +1052,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return NO; } @@ -1079,7 +1064,7 @@ if (changeEncoding) [mySQLConnection restoreStoredEncoding]; - isWorking = NO; + pthread_mutex_unlock(&dataProcessingLock); return YES; } @@ -1364,7 +1349,18 @@ if (tableCreateSyntax) [tableCreateSyntax release]; if (mySQLConnection) [mySQLConnection release]; + pthread_mutex_destroy(&dataProcessingLock); + [super dealloc]; } -@end +#pragma mark - +#pragma mark Private API + +- (void)_loopWhileWorking +{ + while (pthread_mutex_trylock(&dataProcessingLock)) usleep(10000); + pthread_mutex_unlock(&dataProcessingLock); +} + +@end \ No newline at end of file diff --git a/Source/SPTableStructureDelegate.m b/Source/SPTableStructureDelegate.m index 17ad817d..729091d6 100644 --- a/Source/SPTableStructureDelegate.m +++ b/Source/SPTableStructureDelegate.m @@ -49,7 +49,10 @@ NSInteger end = [enc length] - start - 1; collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, end)]]; } else { - if([tableDataInstance tableEncoding] != nil) { + + // If the structure has loaded (not still loading!) and the table encoding + // is set, use the appropriate collations. + if([tableDocumentInstance structureLoaded] && [tableDataInstance tableEncoding] != nil) { collations = [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]]; } else { collations = [NSArray array]; diff --git a/Source/SPTableTriggers.h b/Source/SPTableTriggers.h index 0dfd6f30..70eeeb2e 100644 --- a/Source/SPTableTriggers.h +++ b/Source/SPTableTriggers.h @@ -65,6 +65,7 @@ @property (readwrite, assign) MCPConnection *connection; - (void)loadTriggers; +- (void)resetInterface; // IB action methods - (IBAction)addTrigger:(id)sender; diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m index 44a17549..6462a43f 100644 --- a/Source/SPTableTriggers.m +++ b/Source/SPTableTriggers.m @@ -111,17 +111,7 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; { BOOL enableInteraction = ((![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableTriggers]) || (![tableDocumentInstance isWorking])); - // Disable all interface elements by default - [addTriggerButton setEnabled:NO]; - [refreshTriggersButton setEnabled:NO]; - [triggersTableView setEnabled:NO]; - [labelTextField setStringValue:@""]; - - // Show a warning if the version of MySQL is too low to support triggers - if (![[tableDocumentInstance serverSupport] supportsTriggers]) { - [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")]; - return; - } + [self resetInterface]; // If no item is selected, or the item selected is not a table, return. if (![tablesListInstance tableName] || [tablesListInstance tableType] != SPTableTypeTable) @@ -139,6 +129,26 @@ static const NSString *SPTriggerSQLMode = @"TriggerSQLMode"; [self _refreshTriggerDataForcingCacheRefresh:NO]; } +/** + * Reset the trigger interface, as for no selected table. + */ +- (void)resetInterface +{ + [triggerData removeAllObjects]; + [triggersTableView noteNumberOfRowsChanged]; + + // Disable all interface elements by default + [addTriggerButton setEnabled:NO]; + [refreshTriggersButton setEnabled:NO]; + [triggersTableView setEnabled:NO]; + [labelTextField setStringValue:@""]; + + // Show a warning if the version of MySQL is too low to support triggers + if (![[tableDocumentInstance serverSupport] supportsTriggers]) { + [labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")]; + } +} + #pragma mark - #pragma mark IB action methods -- cgit v1.2.3