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_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

View file

@ -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'.<br />
* 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.<br />
* 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.<br />
* <br />
* 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.<br />
* 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.<br />
* retrieved asynchronously.<br />
* 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
/** <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
* 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/NSEnumerator.h>
#import <Foundation/NSException.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSHashTable.h>
#import <Foundation/NSMapTable.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSNull.h>
@ -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

View file

@ -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