From dd264972c99ef963303597206c7f2e533a3c6e0b Mon Sep 17 00:00:00 2001 From: rfm Date: Thu, 19 Jun 2014 21:26:25 +0000 Subject: [PATCH] initial thread pool implementation git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@37950 72102866-910b-0410-8b05-ffd578937521 --- GNUmakefile | 2 +- SQLClient.h | 68 +++++++++- SQLClient.m | 346 +++++++++++-------------------------------------- testPostgres.m | 7 +- 4 files changed, 143 insertions(+), 280 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 70078b3..e254a7d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -36,7 +36,7 @@ DOCUMENT_NAME=SQLClient SQLClient_INTERFACE_VERSION=1.7 -SQLClient_OBJC_FILES = SQLClient.m +SQLClient_OBJC_FILES = SQLClient.m SQLClientPool.m SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance SQLClient_HEADER_FILES = SQLClient.h SQLClient_AGSDOC_FILES = SQLClient.h diff --git a/SQLClient.h b/SQLClient.h index 9658a00..c2608a3 100644 --- a/SQLClient.h +++ b/SQLClient.h @@ -375,6 +375,7 @@ SQLCLIENT_PRIVATE * This should only be modified by the -setShouldTrim: method. */ BOOL _shouldTrim; /** Should whitespace be trimmed? */ + BOOL _forUseInPool; /** Should be used in a pool only */ NSString *_name; /** Unique identifier for instance */ NSString *_client; /** Identifier within backend */ NSString *_database; /** The configured database name/host */ @@ -622,6 +623,13 @@ SQLCLIENT_PRIVATE */ - (id) initWithConfiguration: (NSDictionary*)config; +/** + * Calls -initWithConfiguration:name:pool: passing NO to say the client is + * not in a pool. + */ +- (id) initWithConfiguration: (NSDictionary*)config + name: (NSString*)reference; + /** * Initialise using the supplied configuration, or if that is nil, try to * use values from NSUserDefaults (and automatically update when the @@ -630,10 +638,10 @@ SQLCLIENT_PRIVATE * a nil name is supplied, defaults to the value of SQLClientName in the * configuration dictionary (or in the standard user defaults). If there is * no value for SQLClientName, uses the string 'Database'.
- * If a SQLClient instance already exists with the name used for this - * instance, the receiver is deallocated and the existing instance is - * retained and returned ... there may only ever be one instance for a - * particular reference name.
+ * If forUseInPool is NO and a SQLClient instance already exists with the + * name used for this instance, the receiver is deallocated and the existing + * instance is retained and returned ... there may only ever be one instance + * for a particular reference name which is not in a pool.
*
* The config argument (or the SQLClientReferences user default) * is a dictionary with names as keys and dictionaries @@ -653,7 +661,8 @@ SQLCLIENT_PRIVATE * connect to a database on a different host over the network. */ - (id) initWithConfiguration: (NSDictionary*)config - name: (NSString*)reference; + name: (NSString*)reference + pool: (BOOL)forUseInPool; /** Two clients are considered equal if they refer to the same database * and are logged in as the same database user using the same protocol. @@ -1367,13 +1376,60 @@ SQLCLIENT_PRIVATE * asynchronous query to update them will be run on the cache thread.
* The rule is that, if the item's age is more than twice its nominal * lifetime, it will be retrieved immediately, otherwise it will be - * retrieved asynchrnonously.
+ * retrieved asynchronously.
* Currently this may only be the main thread or nil. Any attempt to set * another thread will use the main thread instead. */ - (void) setCacheThread: (NSThread*)aThread; @end +/**

An SQLClientPool instance may be used to create/control a pool of + * client objects. Code may obtain autoreleased proxies to the clients + * from the pool and use them safe in the knowledge that they won't be + * used anywhere else ... as soon as the proxy is deallocated the client + * is returned to the pool. + *

+ *

All clients in the pool share the same cache object, so query results + * cached by one client will be available to other clients in the pool. + *

+ */ +@interface SQLClientPool : NSObject +{ + NSConditionLock *lock; /** Controls access to the pool contents */ + SQLClient **c; /** The clients of the pool. */ + SQLClient **p; /** The proxies of the pool. */ + int max; /** Maximum connection count */ + int min; /** Minimum connection count */ +} + +/** + * Calls -initWithConfiguration:name:pool: passing NO to say the client is + * not in a pool. + */ +- (id) initWithConfiguration: (NSDictionary*)config + name: (NSString*)reference + max: (int)maxConnections + min: (int)minConnections; + +/** Fetches an (autoreleased) proxy to a client from the pool. + */ +- (SQLClient*) provideClient; + +/** + * Sets the cache for all the clients in the pool. + */ +- (void) setCache: (GSCache*)aCache; + +/** Takes the client form the provided proxy and places it back + * in the queue (so the proxy stops using it). This happens automatically + * when the proxy is deallocated so you don't generally needs to do it. + * Returns YES if the supplied proxy referred to a client in the pool, + * NO otherwise. + */ +- (BOOL) swallowClient: (SQLClient*)proxy; + +@end + /** * The SQLTransaction transaction class provides a convenient mechanism * for grouping together a series of SQL statements to be executed as a diff --git a/SQLClient.m b/SQLClient.m index a105157..e6f011e 100644 --- a/SQLClient.m +++ b/SQLClient.m @@ -36,9 +36,9 @@ #import #import #import -#import #import #import +#import #import #import #import @@ -105,237 +105,6 @@ static Class NSSetClass = 0; } @end -@interface SQLClientPool : NSObject -{ - unsigned pool; - NSString *name; - NSString *serv; - NSString *user; - NSString *pass; - NSString *path; - NSHashTable *idle; - NSHashTable *used; -} -- (BOOL) isSingle; -- (BOOL) makeIdle: (SQLClient*)c; -- (BOOL) makeUsed: (SQLClient*)c; -- (void) setConfiguration: (NSDictionary*)o; -@end - -@implementation SQLClientPool -- (void) dealloc -{ - if (idle != 0) - { - NSFreeHashTable(idle); - idle = 0; - } - if (used != 0) - { - NSFreeHashTable(used); - used = 0; - } - [name release]; name = nil; - [serv release]; serv = nil; - [user release]; user = nil; - [pass release]; pass = nil; - [path release]; path = nil; - [super dealloc]; -} - -- (id) initWithConfiguration: (NSDictionary*)config - name: (NSString*)reference -{ - name = [reference copy]; - idle = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 16); - used = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 16); - [self setConfiguration: config]; - return self; -} - -- (BOOL) isSingle -{ - if (pool == 1) - { - return YES; - } - return NO; -} - -- (BOOL) makeIdle: (SQLClient*)c -{ - if (NSHashGet(idle, (void*)c) == (void*)c) - { - return YES; // Already idle - } - if (NSHashGet(used, (void*)c) == (void*)c) - { - NSHashRemove(used, (void*)c); - } - if (NSCountHashTable(idle) + NSCountHashTable(used) < pool) - { - NSHashInsert(idle, (void*)c); - return YES; - } - return NO; -} - -- (BOOL) makeUsed: (SQLClient*)c -{ - if (NSHashGet(used, (void*)c) == (void*)c) - { - return YES; // Already used - } - if (NSHashGet(idle, (void*)c) == (void*)c) - { - NSHashRemove(idle, (void*)c); - } - if (NSCountHashTable(idle) + NSCountHashTable(used) < pool) - { - NSHashInsert(used, (void*)c); - return YES; - } - return NO; -} - -- (void) setConfiguration: (NSDictionary*)o -{ - NSDictionary *d; - NSString *s; - BOOL change = NO; - int capacity; - - /* - * get dictionary containing config info for this client by name. - */ - d = [o objectForKey: @"SQLClientReferences"]; - if ([d isKindOfClass: [NSDictionary class]] == NO) - { - d = nil; - } - d = [d objectForKey: name]; - if ([d isKindOfClass: [NSDictionary class]] == NO) - { - d = nil; - } - - s = [d objectForKey: @"ServerType"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = @"Postgres"; - } - if (s != serv && [s isEqual: serv] == NO) - { - s = [s copy]; - [serv release]; - serv = s; - change = YES; - } - - s = [d objectForKey: @"Database"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = [o objectForKey: @"Database"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = nil; - } - } - if (s != path && [s isEqual: path] == NO) - { - s = [s copy]; - [path release]; - path = s; - change = YES; - } - - s = [d objectForKey: @"User"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = [o objectForKey: @"User"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = @""; - } - } - if (s != user && [s isEqual: user] == NO) - { - s = [s copy]; - [user release]; - user = s; - change = YES; - } - - s = [d objectForKey: @"Password"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = [o objectForKey: @"Password"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = @""; - } - } - if (s != pass && [s isEqual: pass] == NO) - { - s = [s copy]; - [pass release]; - pass = s; - change = YES; - } - - s = [d objectForKey: @"Password"]; - if ([s isKindOfClass: NSStringClass] == NO) - { - s = @"1"; - } - capacity = [s intValue]; - if (capacity < 1) capacity = 1; - if (capacity > 100) capacity = 100; - - if (change == YES) - { - NSResetHashTable(idle); - NSResetHashTable(used); - } - if (pool > capacity) - { - unsigned ic = NSCountHashTable(idle); - unsigned uc = NSCountHashTable(used); - - if (ic + uc > capacity) - { - NSHashEnumerator e = NSEnumerateHashTable(idle); - void *c; - - while (ic + uc > capacity - && (c = NSNextHashEnumeratorItem(&e)) != nil) - { - NSHashRemove(idle, c); - ic--; - } - NSEndHashTableEnumeration(&e); - if (uc > capacity) - { - NSHashEnumerator e = NSEnumerateHashTable(used); - void *c; - - while (uc > capacity - && (c = NSNextHashEnumeratorItem(&e)) != nil) - { - NSHashRemove(used, c); - uc--; - } - NSEndHashTableEnumeration(&e); - } - } - } - pool = capacity; -} - -@end - - - static Class aClass = 0; static Class rClass = 0; @@ -866,8 +635,9 @@ static NSTimeInterval classDuration = -1; /** * Container for all instances. */ +static NSHashTable *clientsHash = 0; static NSMapTable *clientsMap = 0; -static NSRecursiveLock *clientsMapLock = nil; +static NSRecursiveLock *clientsLock = nil; static NSString *beginString = @"begin"; static NSArray *beginStatement = nil; static NSString *commitString = @"commit"; @@ -898,11 +668,19 @@ static unsigned int maxConnections = 8; + (NSArray*) allClients { - NSArray *a; + NSMutableArray *a; + NSHashEnumerator e; + id o; - [clientsMapLock lock]; - a = NSAllMapTableValues(clientsMap); - [clientsMapLock unlock]; + [clientsLock lock]; + a = [NSMutableArray arrayWithCapacity: NSCountHashTable(clientsHash)]; + e = NSEnumerateHashTable(clientsHash); + while (nil != (o = (id)NSNextHashEnumeratorItem(&e))) + { + [a addObject: o]; + } + NSEndHashTableEnumeration(&e); + [clientsLock unlock]; return a; } @@ -951,10 +729,10 @@ static unsigned int maxConnections = 8; } } - [clientsMapLock lock]; + [clientsLock lock]; existing = (SQLClient*)NSMapGet(clientsMap, reference); [[existing retain] autorelease]; - [clientsMapLock unlock]; + [clientsLock unlock]; return existing; } @@ -966,11 +744,12 @@ static unsigned int maxConnections = 8; queryModes = [[NSArray alloc] initWithObjects: modes count: 1]; GSTickerTimeNow(); [SQLRecord class]; // Force initialisation - if (clientsMap == 0) + if (0 == clientsHash) { + clientsHash = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0); clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 0); - clientsMapLock = [NSRecursiveLock new]; + clientsLock = [NSRecursiveLock new]; beginStatement = [[NSArray arrayWithObject: beginString] retain]; commitStatement = [[NSArray arrayWithObject: commitString] retain]; rollbackStatement = [[NSArray arrayWithObject: rollbackString] retain]; @@ -992,15 +771,14 @@ static unsigned int maxConnections = 8; + (void) purgeConnections: (NSDate*)since { - NSMapEnumerator e; - NSString *n; + NSHashEnumerator e; SQLClient *o; unsigned int connectionCount = 0; NSTimeInterval t = [since timeIntervalSinceReferenceDate]; - [clientsMapLock lock]; - e = NSEnumerateMapTable(clientsMap); - while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o) != 0) + [clientsLock lock]; + e = NSEnumerateHashTable(clientsHash); + while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e))) { if (since != nil) { @@ -1016,8 +794,8 @@ static unsigned int maxConnections = 8; connectionCount++; } } - NSEndMapTableEnumeration(&e); - [clientsMapLock unlock]; + NSEndHashTableEnumeration(&e); + [clientsLock unlock]; while (connectionCount >= maxConnections) { @@ -1025,9 +803,9 @@ static unsigned int maxConnections = 8; NSTimeInterval oldest = 0.0; connectionCount = 0; - [clientsMapLock lock]; - e = NSEnumerateMapTable(clientsMap); - while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o)) + [clientsLock lock]; + e = NSEnumerateHashTable(clientsHash); + while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e))) { if ([o connected] == YES) { @@ -1041,8 +819,8 @@ static unsigned int maxConnections = 8; } } } - NSEndMapTableEnumeration(&e); - [clientsMapLock unlock]; + NSEndHashTableEnumeration(&e); + [clientsLock unlock]; connectionCount--; if ([other debugging] > 0) { @@ -1233,12 +1011,13 @@ static unsigned int maxConnections = 8; { NSNotificationCenter *nc; - if (_name != nil) + [clientsLock lock]; + NSHashRemove(clientsHash, (void*)self); + if (_name != nil && NO == _forUseInPool) { - [clientsMapLock lock]; NSMapRemove(clientsMap, (void*)_name); - [clientsMapLock unlock]; } + [clientsLock unlock]; nc = [NSNotificationCenter defaultCenter]; [nc removeObserver: self]; [self disconnect]; @@ -1381,6 +1160,14 @@ static unsigned int maxConnections = 8; - (id) initWithConfiguration: (NSDictionary*)config name: (NSString*)reference +{ + return [self initWithConfiguration: config name: reference pool: NO]; +} + + +- (id) initWithConfiguration: (NSDictionary*)config + name: (NSString*)reference + pool: (BOOL)forUseInPool { NSNotification *n; NSDictionary *conf = config; @@ -1401,8 +1188,16 @@ static unsigned int maxConnections = 8; } } - [clientsMapLock lock]; - existing = (SQLClient*)NSMapGet(clientsMap, reference); + [clientsLock lock]; + _forUseInPool = (NO == forUseInPool) ? NO : YES; + if (YES == _forUseInPool) + { + existing = (SQLClient*)NSMapGet(clientsMap, reference); + } + else + { + existing = nil; + } if (nil == existing) { lock = [NSRecursiveLock new]; // Ensure thread-safety. @@ -1426,6 +1221,7 @@ static unsigned int maxConnections = 8; object: conf userInfo: nil]; + NSHashInsert(clientsHash, (void*)self); [self _configure: n]; // Actually set up the configuration. } else @@ -1433,7 +1229,7 @@ static unsigned int maxConnections = 8; [self release]; self = [existing retain]; } - [clientsMapLock unlock]; + [clientsLock unlock]; return self; } @@ -1733,12 +1529,12 @@ static unsigned int maxConnections = 8; * it from the table so that no other thread will find it * and try to use it while it is being deallocated. */ - [clientsMapLock lock]; + [clientsLock lock]; if (NSDecrementExtraRefCountWasZero(self)) { [self dealloc]; } - [clientsMapLock unlock]; + [clientsLock unlock]; } - (void) rollback @@ -1803,24 +1599,27 @@ static unsigned int maxConnections = 8; { if ([s isEqual: _name] == NO) { - [clientsMapLock lock]; - if (NSMapGet(clientsMap, s) != 0) + [clientsLock lock]; + if (NO == _forUseInPool) { - [clientsMapLock unlock]; - [lock unlock]; - if ([self debugging] > 0) + if (NSMapGet(clientsMap, s) != 0) { - [self debug: @"Error attempt to re-use client name %@", s]; + [clientsLock unlock]; + [lock unlock]; + if ([self debugging] > 0) + { + [self + debug: @"Error attempt to re-use client name %@", s]; + } + NS_VOIDRETURN; } - NS_VOIDRETURN; } if (connected == YES) { [self disconnect]; } - if (_name != nil) + if (NO == _forUseInPool && _name != nil) { - [[self retain] autorelease]; NSMapRemove(clientsMap, (void*)_name); } s = [s copy]; @@ -1828,8 +1627,11 @@ static unsigned int maxConnections = 8; _name = s; [_client release]; _client = [[[NSProcessInfo processInfo] globallyUniqueString] retain]; - NSMapInsert(clientsMap, (void*)_name, (void*)self); - [clientsMapLock unlock]; + if (NO == _forUseInPool && _name != nil) + { + NSMapInsert(clientsMap, (void*)_name, (void*)self); + } + [clientsLock unlock]; } } NS_HANDLER diff --git a/testPostgres.m b/testPostgres.m index c7b4d0d..0b221e2 100644 --- a/testPostgres.m +++ b/testPostgres.m @@ -42,6 +42,7 @@ int main() { NSAutoreleasePool *pool = [NSAutoreleasePool new]; + SQLClientPool *sp; SQLClient *db; NSUserDefaults *defs; NSMutableArray *records; @@ -68,7 +69,11 @@ main() nil] ]; - db = [SQLClient clientWithConfiguration: nil name: @"test"]; + sp = [[SQLClientPool alloc] initWithConfiguration: nil + name: @"test" + max: 2 + min: 1]; + db = [[sp autorelease] provideClient]; l = [Logger new]; [[NSNotificationCenter defaultCenter] addObserver: l