initial thread pool implementation

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@37950 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2014-06-19 21:26:25 +00:00
parent c738aae5bc
commit dd264972c9
4 changed files with 143 additions and 280 deletions

View file

@ -36,7 +36,7 @@ DOCUMENT_NAME=SQLClient
SQLClient_INTERFACE_VERSION=1.7 SQLClient_INTERFACE_VERSION=1.7
SQLClient_OBJC_FILES = SQLClient.m SQLClient_OBJC_FILES = SQLClient.m SQLClientPool.m
SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance
SQLClient_HEADER_FILES = SQLClient.h SQLClient_HEADER_FILES = SQLClient.h
SQLClient_AGSDOC_FILES = SQLClient.h SQLClient_AGSDOC_FILES = SQLClient.h

View file

@ -375,6 +375,7 @@ SQLCLIENT_PRIVATE
* This should only be modified by the -setShouldTrim: method. * This should only be modified by the -setShouldTrim: method.
*/ */
BOOL _shouldTrim; /** Should whitespace be trimmed? */ BOOL _shouldTrim; /** Should whitespace be trimmed? */
BOOL _forUseInPool; /** Should be used in a pool only */
NSString *_name; /** Unique identifier for instance */ NSString *_name; /** Unique identifier for instance */
NSString *_client; /** Identifier within backend */ NSString *_client; /** Identifier within backend */
NSString *_database; /** The configured database name/host */ NSString *_database; /** The configured database name/host */
@ -622,6 +623,13 @@ SQLCLIENT_PRIVATE
*/ */
- (id) initWithConfiguration: (NSDictionary*)config; - (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 * Initialise using the supplied configuration, or if that is nil, try to
* use values from NSUserDefaults (and automatically update when the * 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 * a nil name is supplied, defaults to the value of SQLClientName in the
* configuration dictionary (or in the standard user defaults). If there is * configuration dictionary (or in the standard user defaults). If there is
* no value for SQLClientName, uses the string 'Database'.<br /> * no value for SQLClientName, uses the string 'Database'.<br />
* If a SQLClient instance already exists with the name used for this * If forUseInPool is NO and a SQLClient instance already exists with the
* instance, the receiver is deallocated and the existing instance is * name used for this instance, the receiver is deallocated and the existing
* retained and returned ... there may only ever be one instance for a * instance is retained and returned ... there may only ever be one instance
* particular reference name.<br /> * for a particular reference name which is not in a pool.<br />
* <br /> * <br />
* The config argument (or the SQLClientReferences user default) * The config argument (or the SQLClientReferences user default)
* is a dictionary with names as keys and dictionaries * 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. * connect to a database on a different host over the network.
*/ */
- (id) initWithConfiguration: (NSDictionary*)config - (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 /** 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. * 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.<br /> * asynchronous query to update them will be run on the cache thread.<br />
* The rule is that, if the item's age is more than twice its nominal * 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 * lifetime, it will be retrieved immediately, otherwise it will be
* retrieved asynchrnonously.<br /> * retrieved asynchronously.<br />
* Currently this may only be the main thread or nil. Any attempt to set * Currently this may only be the main thread or nil. Any attempt to set
* another thread will use the main thread instead. * another thread will use the main thread instead.
*/ */
- (void) setCacheThread: (NSThread*)aThread; - (void) setCacheThread: (NSThread*)aThread;
@end @end
/** <p>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.
* </p>
* <p>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.
* </p>
*/
@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 * The SQLTransaction transaction class provides a convenient mechanism
* for grouping together a series of SQL statements to be executed as a * for grouping together a series of SQL statements to be executed as a

View file

@ -36,9 +36,9 @@
#import <Foundation/NSDictionary.h> #import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h> #import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h> #import <Foundation/NSException.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSKeyValueCoding.h> #import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSLock.h> #import <Foundation/NSLock.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSMapTable.h> #import <Foundation/NSMapTable.h>
#import <Foundation/NSNotification.h> #import <Foundation/NSNotification.h>
#import <Foundation/NSNull.h> #import <Foundation/NSNull.h>
@ -105,237 +105,6 @@ static Class NSSetClass = 0;
} }
@end @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 aClass = 0;
static Class rClass = 0; static Class rClass = 0;
@ -866,8 +635,9 @@ static NSTimeInterval classDuration = -1;
/** /**
* Container for all instances. * Container for all instances.
*/ */
static NSHashTable *clientsHash = 0;
static NSMapTable *clientsMap = 0; static NSMapTable *clientsMap = 0;
static NSRecursiveLock *clientsMapLock = nil; static NSRecursiveLock *clientsLock = nil;
static NSString *beginString = @"begin"; static NSString *beginString = @"begin";
static NSArray *beginStatement = nil; static NSArray *beginStatement = nil;
static NSString *commitString = @"commit"; static NSString *commitString = @"commit";
@ -898,11 +668,19 @@ static unsigned int maxConnections = 8;
+ (NSArray*) allClients + (NSArray*) allClients
{ {
NSArray *a; NSMutableArray *a;
NSHashEnumerator e;
id o;
[clientsMapLock lock]; [clientsLock lock];
a = NSAllMapTableValues(clientsMap); a = [NSMutableArray arrayWithCapacity: NSCountHashTable(clientsHash)];
[clientsMapLock unlock]; e = NSEnumerateHashTable(clientsHash);
while (nil != (o = (id)NSNextHashEnumeratorItem(&e)))
{
[a addObject: o];
}
NSEndHashTableEnumeration(&e);
[clientsLock unlock];
return a; return a;
} }
@ -951,10 +729,10 @@ static unsigned int maxConnections = 8;
} }
} }
[clientsMapLock lock]; [clientsLock lock];
existing = (SQLClient*)NSMapGet(clientsMap, reference); existing = (SQLClient*)NSMapGet(clientsMap, reference);
[[existing retain] autorelease]; [[existing retain] autorelease];
[clientsMapLock unlock]; [clientsLock unlock];
return existing; return existing;
} }
@ -966,11 +744,12 @@ static unsigned int maxConnections = 8;
queryModes = [[NSArray alloc] initWithObjects: modes count: 1]; queryModes = [[NSArray alloc] initWithObjects: modes count: 1];
GSTickerTimeNow(); GSTickerTimeNow();
[SQLRecord class]; // Force initialisation [SQLRecord class]; // Force initialisation
if (clientsMap == 0) if (0 == clientsHash)
{ {
clientsHash = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0);
clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks, clientsMap = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonRetainedObjectMapValueCallBacks, 0); NSNonRetainedObjectMapValueCallBacks, 0);
clientsMapLock = [NSRecursiveLock new]; clientsLock = [NSRecursiveLock new];
beginStatement = [[NSArray arrayWithObject: beginString] retain]; beginStatement = [[NSArray arrayWithObject: beginString] retain];
commitStatement = [[NSArray arrayWithObject: commitString] retain]; commitStatement = [[NSArray arrayWithObject: commitString] retain];
rollbackStatement = [[NSArray arrayWithObject: rollbackString] retain]; rollbackStatement = [[NSArray arrayWithObject: rollbackString] retain];
@ -992,15 +771,14 @@ static unsigned int maxConnections = 8;
+ (void) purgeConnections: (NSDate*)since + (void) purgeConnections: (NSDate*)since
{ {
NSMapEnumerator e; NSHashEnumerator e;
NSString *n;
SQLClient *o; SQLClient *o;
unsigned int connectionCount = 0; unsigned int connectionCount = 0;
NSTimeInterval t = [since timeIntervalSinceReferenceDate]; NSTimeInterval t = [since timeIntervalSinceReferenceDate];
[clientsMapLock lock]; [clientsLock lock];
e = NSEnumerateMapTable(clientsMap); e = NSEnumerateHashTable(clientsHash);
while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o) != 0) while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e)))
{ {
if (since != nil) if (since != nil)
{ {
@ -1016,8 +794,8 @@ static unsigned int maxConnections = 8;
connectionCount++; connectionCount++;
} }
} }
NSEndMapTableEnumeration(&e); NSEndHashTableEnumeration(&e);
[clientsMapLock unlock]; [clientsLock unlock];
while (connectionCount >= maxConnections) while (connectionCount >= maxConnections)
{ {
@ -1025,9 +803,9 @@ static unsigned int maxConnections = 8;
NSTimeInterval oldest = 0.0; NSTimeInterval oldest = 0.0;
connectionCount = 0; connectionCount = 0;
[clientsMapLock lock]; [clientsLock lock];
e = NSEnumerateMapTable(clientsMap); e = NSEnumerateHashTable(clientsHash);
while (NSNextMapEnumeratorPair(&e, (void**)&n, (void**)&o)) while (nil != (o = (SQLClient*)NSNextHashEnumeratorItem(&e)))
{ {
if ([o connected] == YES) if ([o connected] == YES)
{ {
@ -1041,8 +819,8 @@ static unsigned int maxConnections = 8;
} }
} }
} }
NSEndMapTableEnumeration(&e); NSEndHashTableEnumeration(&e);
[clientsMapLock unlock]; [clientsLock unlock];
connectionCount--; connectionCount--;
if ([other debugging] > 0) if ([other debugging] > 0)
{ {
@ -1233,12 +1011,13 @@ static unsigned int maxConnections = 8;
{ {
NSNotificationCenter *nc; NSNotificationCenter *nc;
if (_name != nil) [clientsLock lock];
NSHashRemove(clientsHash, (void*)self);
if (_name != nil && NO == _forUseInPool)
{ {
[clientsMapLock lock];
NSMapRemove(clientsMap, (void*)_name); NSMapRemove(clientsMap, (void*)_name);
[clientsMapLock unlock];
} }
[clientsLock unlock];
nc = [NSNotificationCenter defaultCenter]; nc = [NSNotificationCenter defaultCenter];
[nc removeObserver: self]; [nc removeObserver: self];
[self disconnect]; [self disconnect];
@ -1381,6 +1160,14 @@ static unsigned int maxConnections = 8;
- (id) initWithConfiguration: (NSDictionary*)config - (id) initWithConfiguration: (NSDictionary*)config
name: (NSString*)reference name: (NSString*)reference
{
return [self initWithConfiguration: config name: reference pool: NO];
}
- (id) initWithConfiguration: (NSDictionary*)config
name: (NSString*)reference
pool: (BOOL)forUseInPool
{ {
NSNotification *n; NSNotification *n;
NSDictionary *conf = config; NSDictionary *conf = config;
@ -1401,8 +1188,16 @@ static unsigned int maxConnections = 8;
} }
} }
[clientsMapLock lock]; [clientsLock lock];
existing = (SQLClient*)NSMapGet(clientsMap, reference); _forUseInPool = (NO == forUseInPool) ? NO : YES;
if (YES == _forUseInPool)
{
existing = (SQLClient*)NSMapGet(clientsMap, reference);
}
else
{
existing = nil;
}
if (nil == existing) if (nil == existing)
{ {
lock = [NSRecursiveLock new]; // Ensure thread-safety. lock = [NSRecursiveLock new]; // Ensure thread-safety.
@ -1426,6 +1221,7 @@ static unsigned int maxConnections = 8;
object: conf object: conf
userInfo: nil]; userInfo: nil];
NSHashInsert(clientsHash, (void*)self);
[self _configure: n]; // Actually set up the configuration. [self _configure: n]; // Actually set up the configuration.
} }
else else
@ -1433,7 +1229,7 @@ static unsigned int maxConnections = 8;
[self release]; [self release];
self = [existing retain]; self = [existing retain];
} }
[clientsMapLock unlock]; [clientsLock unlock];
return self; return self;
} }
@ -1733,12 +1529,12 @@ static unsigned int maxConnections = 8;
* it from the table so that no other thread will find it * it from the table so that no other thread will find it
* and try to use it while it is being deallocated. * and try to use it while it is being deallocated.
*/ */
[clientsMapLock lock]; [clientsLock lock];
if (NSDecrementExtraRefCountWasZero(self)) if (NSDecrementExtraRefCountWasZero(self))
{ {
[self dealloc]; [self dealloc];
} }
[clientsMapLock unlock]; [clientsLock unlock];
} }
- (void) rollback - (void) rollback
@ -1803,24 +1599,27 @@ static unsigned int maxConnections = 8;
{ {
if ([s isEqual: _name] == NO) if ([s isEqual: _name] == NO)
{ {
[clientsMapLock lock]; [clientsLock lock];
if (NSMapGet(clientsMap, s) != 0) if (NO == _forUseInPool)
{ {
[clientsMapLock unlock]; if (NSMapGet(clientsMap, s) != 0)
[lock unlock];
if ([self debugging] > 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) if (connected == YES)
{ {
[self disconnect]; [self disconnect];
} }
if (_name != nil) if (NO == _forUseInPool && _name != nil)
{ {
[[self retain] autorelease];
NSMapRemove(clientsMap, (void*)_name); NSMapRemove(clientsMap, (void*)_name);
} }
s = [s copy]; s = [s copy];
@ -1828,8 +1627,11 @@ static unsigned int maxConnections = 8;
_name = s; _name = s;
[_client release]; [_client release];
_client = [[[NSProcessInfo processInfo] globallyUniqueString] retain]; _client = [[[NSProcessInfo processInfo] globallyUniqueString] retain];
NSMapInsert(clientsMap, (void*)_name, (void*)self); if (NO == _forUseInPool && _name != nil)
[clientsMapLock unlock]; {
NSMapInsert(clientsMap, (void*)_name, (void*)self);
}
[clientsLock unlock];
} }
} }
NS_HANDLER NS_HANDLER

View file

@ -42,6 +42,7 @@ int
main() main()
{ {
NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSAutoreleasePool *pool = [NSAutoreleasePool new];
SQLClientPool *sp;
SQLClient *db; SQLClient *db;
NSUserDefaults *defs; NSUserDefaults *defs;
NSMutableArray *records; NSMutableArray *records;
@ -68,7 +69,11 @@ main()
nil] 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]; l = [Logger new];
[[NSNotificationCenter defaultCenter] addObserver: l [[NSNotificationCenter defaultCenter] addObserver: l