mirror of
https://github.com/gnustep/libs-sqlclient.git
synced 2025-02-20 18:32:06 +00:00
Add mechanism to update cache only in main thread.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@26117 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
43f1c9d6d6
commit
833a815c96
3 changed files with 257 additions and 69 deletions
11
ChangeLog
11
ChangeLog
|
@ -1,3 +1,14 @@
|
|||
2008-02-21 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* SQLClient.h:
|
||||
* SQLClient.m:
|
||||
Experimental new method to set a thread to do all cached
|
||||
queries on and to perform asynchronous updates if other
|
||||
threads request information which is in the cache but
|
||||
past its expiry date. Should allow threads to use
|
||||
config information from a database without blocking
|
||||
unnecessarily.
|
||||
|
||||
2008-02-15 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* SQLClient.m: Fix memory leak when executing transaction.
|
||||
|
|
48
SQLClient.h
48
SQLClient.h
|
@ -190,6 +190,7 @@
|
|||
@class NSMutableSet;
|
||||
@class NSRecursiveLock;
|
||||
@class NSString;
|
||||
@class NSThread;
|
||||
@class SQLTransaction;
|
||||
|
||||
/**
|
||||
|
@ -377,6 +378,7 @@ extern unsigned SQLClientTimeTick();
|
|||
NSTimeInterval _duration;
|
||||
unsigned int _debugging; /** The current debugging level */
|
||||
GSCache *_cache; /** The cache for query results */
|
||||
NSThread *_cacheThread; /** Thread for cache queries */
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1097,7 +1099,7 @@ extern unsigned SQLClientTimeTick();
|
|||
@end
|
||||
|
||||
/**
|
||||
* This category porovides methods for caching the results of queries
|
||||
* This category provides methods for caching the results of queries
|
||||
* in order to reduce the number of client-server trips and the database
|
||||
* load produced by an application which needs update its information
|
||||
* from the database frequently.
|
||||
|
@ -1106,30 +1108,20 @@ extern unsigned SQLClientTimeTick();
|
|||
|
||||
/**
|
||||
* Returns the cache used by the receiver for storing the results of
|
||||
* requests made through it.
|
||||
* requests made through it. Creates a new cache if necessary.
|
||||
*/
|
||||
- (GSCache*) cache;
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
* whether it is already cached, and its absolute value is used to
|
||||
* set the lifetime of the results.<br />
|
||||
* If seconds is zero, the cache for this query is emptied.
|
||||
* Calls -cache:simpleQuery:recordClass: with the default record class and
|
||||
* wth a query string formed from stmt and the following values (if any).
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt,...;
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
* whether it is already cached, and its absolute value is used to
|
||||
* set the lifetime of the results.<br />
|
||||
* If seconds is zero, the cache for this query is emptied.
|
||||
* Calls -cache:simpleQuery:recordClass: with the default record class and
|
||||
* wth a query string formed from stmt and values.
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
query: (NSString*)stmt
|
||||
|
@ -1141,7 +1133,7 @@ extern unsigned SQLClientTimeTick();
|
|||
- (NSMutableArray*) cache: (int)seconds simpleQuery: (NSString*)stmt;
|
||||
|
||||
/**
|
||||
* If the result of the query is already cached and is still valid,
|
||||
* If the result of the query is already cached and has not expired,
|
||||
* return it. Otherwise, perform the query and cache the result
|
||||
* giving it the specified lifetime in seconds.<br />
|
||||
* If seconds is negative, the query is performed irrespective of
|
||||
|
@ -1151,7 +1143,14 @@ extern unsigned SQLClientTimeTick();
|
|||
* Handles locking.<br />
|
||||
* Maintains -lastOperation date.<br />
|
||||
* The value of cls must be a class which responds to the
|
||||
* [SQLRecord+newWithValues:keys:count:] method.
|
||||
* [SQLRecord+newWithValues:keys:count:] method.<br />
|
||||
* If a cache thread has been set using the -setCacheThread: method, and the
|
||||
* -cache:simpleQuery:recordClass: method is called from a thread other
|
||||
* than the cache thread, then any query to retrieve uncached data will
|
||||
* be performed in the cache thread, and for cached (but expired) data,
|
||||
* the old (expired) results may be returned ... in which case an
|
||||
* asynchronous query to update the cache will be executed as soon
|
||||
* as possible in the cache thread.
|
||||
*/
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
simpleQuery: (NSString*)stmt
|
||||
|
@ -1165,6 +1164,19 @@ extern unsigned SQLClientTimeTick();
|
|||
*/
|
||||
- (void) setCache: (GSCache*)aCache;
|
||||
|
||||
/** Sets the thread to be used to retrieve data to populate the cache.<br />
|
||||
* All cached queries will be performed in this thread (if non-nil).<br />
|
||||
* The setting of a thread for the cache also implies that expired items in
|
||||
* the cache may not be removed when they are queried from another thread,
|
||||
* rather they can be kept (if they are not <em>too</em> old) and an
|
||||
* 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 />
|
||||
* 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
|
||||
|
||||
/**
|
||||
|
|
267
SQLClient.m
267
SQLClient.m
|
@ -44,8 +44,10 @@
|
|||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSPathUtilities.h>
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSRunLoop.h>
|
||||
#import <Foundation/NSSet.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSThread.h>
|
||||
#import <Foundation/NSTimer.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
@ -70,11 +72,30 @@ NSString * const SQLClientDidDisconnectNotification
|
|||
= @"SQLClientDidDisconnectNotification";
|
||||
|
||||
static NSNull *null = nil;
|
||||
static NSArray *queryModes = nil;
|
||||
static NSThread *mainThread = nil;
|
||||
static Class NSStringClass = 0;
|
||||
static Class NSArrayClass = 0;
|
||||
static Class NSDateClass = 0;
|
||||
static Class NSSetClass = 0;
|
||||
|
||||
@interface CacheQuery : NSObject
|
||||
{
|
||||
@public
|
||||
NSString *query;
|
||||
Class recordClass;
|
||||
unsigned lifetime;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CacheQuery
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(query);
|
||||
[super dealloc];
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SQLClientPool : NSObject
|
||||
{
|
||||
unsigned pool;
|
||||
|
@ -842,11 +863,20 @@ static NSArray *rollbackStatement = nil;
|
|||
|
||||
@interface SQLClient (Private)
|
||||
- (void) _configure: (NSNotification*)n;
|
||||
- (void) _populateCache: (CacheQuery*)a;
|
||||
- (NSArray*) _prepare: (NSString*)stmt args: (va_list)args;
|
||||
- (void) _recordMainThread;
|
||||
- (NSArray*) _substitute: (NSString*)str with: (NSDictionary*)vals;
|
||||
+ (void) _tick: (NSTimer*)t;
|
||||
@end
|
||||
|
||||
@interface SQLClient (GSCacheDelegate)
|
||||
- (BOOL) shouldKeepItem: (id)anObject
|
||||
withKey: (id)aKey
|
||||
lifetime: (unsigned)lifetime
|
||||
after: (unsigned)delay;
|
||||
@end
|
||||
|
||||
@implementation SQLClient
|
||||
|
||||
static unsigned int maxConnections = 8;
|
||||
|
@ -915,6 +945,10 @@ static unsigned int maxConnections = 8;
|
|||
|
||||
+ (void) initialize
|
||||
{
|
||||
static id modes[1];
|
||||
|
||||
modes[0] = NSDefaultRunLoopMode;
|
||||
queryModes = [[NSArray alloc] initWithObjects: modes count: 1];
|
||||
GSTickerTimeNow();
|
||||
[SQLRecord class]; // Force initialisation
|
||||
if (clientsMap == 0)
|
||||
|
@ -1159,6 +1193,7 @@ static unsigned int maxConnections = 8;
|
|||
DESTROY(_name);
|
||||
DESTROY(_statements);
|
||||
DESTROY(_cache);
|
||||
DESTROY(_cacheThread);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
@ -1166,6 +1201,7 @@ static unsigned int maxConnections = 8;
|
|||
{
|
||||
NSMutableString *s = AUTORELEASE([NSMutableString new]);
|
||||
|
||||
[lock lock];
|
||||
[s appendFormat: @"Database - %@\n", [self clientName]];
|
||||
[s appendFormat: @" Name - %@\n", [self name]];
|
||||
[s appendFormat: @" DBase - %@\n", [self database]];
|
||||
|
@ -1182,6 +1218,7 @@ static unsigned int maxConnections = 8;
|
|||
{
|
||||
[s appendFormat: @" Cache - %@\n", _cache];
|
||||
}
|
||||
[lock unlock];
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -2068,6 +2105,32 @@ static unsigned int maxConnections = 8;
|
|||
[self setPassword: s];
|
||||
}
|
||||
|
||||
/** Internal method to populate the cache with the result of a query.
|
||||
*/
|
||||
- (void) _populateCache: (CacheQuery*)a
|
||||
{
|
||||
GSCache *cache;
|
||||
id result;
|
||||
|
||||
[lock lock];
|
||||
NS_DURING
|
||||
{
|
||||
result = [self backendQuery: a->query recordClass: a->recordClass];
|
||||
}
|
||||
NS_HANDLER
|
||||
{
|
||||
[lock unlock];
|
||||
[localException raise];
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
[lock unlock];
|
||||
|
||||
cache = [self cache];
|
||||
[cache setObject: result
|
||||
forKey: a->query
|
||||
lifetime: a->lifetime];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to build an sql string by quoting any non-string objects
|
||||
* and concatenating the resulting strings in a nil terminated list.<br />
|
||||
|
@ -2116,6 +2179,11 @@ static unsigned int maxConnections = 8;
|
|||
return ma;
|
||||
}
|
||||
|
||||
- (void) _recordMainThread
|
||||
{
|
||||
mainThread = [NSThread currentThread];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to substitute values from the dictionary into
|
||||
* a string containing markup identifying where the values should
|
||||
|
@ -2308,6 +2376,39 @@ static unsigned int maxConnections = 8;
|
|||
}
|
||||
@end
|
||||
|
||||
@implementation SQLClient (GSCacheDelegate)
|
||||
- (BOOL) shouldKeepItem: (id)anObject
|
||||
withKey: (id)aKey
|
||||
lifetime: (unsigned)lifetime
|
||||
after: (unsigned)delay
|
||||
{
|
||||
CacheQuery *a;
|
||||
|
||||
a = [CacheQuery new];
|
||||
ASSIGN(a->query, aKey);
|
||||
a->recordClass = [[[NSThread currentThread] threadDictionary]
|
||||
objectForKey: @"SQLClientRecordClass"];
|
||||
a->lifetime = lifetime;
|
||||
AUTORELEASE(a);
|
||||
if (_cacheThread == nil)
|
||||
{
|
||||
[self _populateCache: a];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We schedule an asynchronous update if the item is not too old,
|
||||
* otherwise (more than lifetime seconds past its expiry) we wait
|
||||
* for the update to complete.
|
||||
*/
|
||||
[self performSelectorOnMainThread: @selector(_populateCache:)
|
||||
withObject: a
|
||||
waitUntilDone: (delay > lifetime) ? YES : NO
|
||||
modes: queryModes];
|
||||
}
|
||||
return YES; // Always keep items ...
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation SQLClient(Convenience)
|
||||
|
@ -2439,11 +2540,20 @@ static unsigned int maxConnections = 8;
|
|||
|
||||
- (GSCache*) cache
|
||||
{
|
||||
GSCache *c;
|
||||
|
||||
[lock lock];
|
||||
if (_cache == nil)
|
||||
{
|
||||
_cache = [GSCache new];
|
||||
if (_cacheThread != nil)
|
||||
{
|
||||
[_cache setDelegate: self];
|
||||
}
|
||||
}
|
||||
return _cache;
|
||||
c = RETAIN(_cache);
|
||||
[lock unlock];
|
||||
return AUTORELEASE(c);
|
||||
}
|
||||
|
||||
- (NSMutableArray*) cache: (int)seconds
|
||||
|
@ -2476,79 +2586,134 @@ static unsigned int maxConnections = 8;
|
|||
simpleQuery: (NSString*)stmt
|
||||
recordClass: (Class)cls
|
||||
{
|
||||
NSMutableArray *result = nil;
|
||||
NSMutableArray *result;
|
||||
NSTimeInterval start;
|
||||
GSCache *c;
|
||||
id toCache;
|
||||
|
||||
if (cls == 0)
|
||||
{
|
||||
[NSException raise: NSInvalidArgumentException
|
||||
format: @"nil class passed to cache:simpleQuery:recordClass:"];
|
||||
}
|
||||
[lock lock];
|
||||
NS_DURING
|
||||
{
|
||||
NSTimeInterval start = GSTickerTimeNow();
|
||||
GSCache *c = [self cache];
|
||||
id toCache = nil;
|
||||
|
||||
if (seconds < 0)
|
||||
{
|
||||
seconds = -seconds;
|
||||
[[[NSThread currentThread] threadDictionary] setObject: cls
|
||||
forKey: @"SQLClientRecordClass"];
|
||||
start = GSTickerTimeNow();
|
||||
c = [self cache];
|
||||
toCache = nil;
|
||||
|
||||
if (seconds < 0)
|
||||
{
|
||||
seconds = -seconds;
|
||||
result = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = [c objectForKey: stmt];
|
||||
}
|
||||
|
||||
if (result == nil)
|
||||
{
|
||||
CacheQuery *a;
|
||||
|
||||
a = [CacheQuery new];
|
||||
ASSIGN(a->query, stmt);
|
||||
a->recordClass = cls;
|
||||
a->lifetime = seconds;
|
||||
AUTORELEASE(a);
|
||||
|
||||
if (_cacheThread == nil)
|
||||
{
|
||||
[self _populateCache: a];
|
||||
}
|
||||
else
|
||||
{
|
||||
result = [c objectForKey: stmt];
|
||||
}
|
||||
|
||||
if (result == nil)
|
||||
/* Not really an asynchronous query becuse we wait until it's
|
||||
* done in order to have a result we can return.
|
||||
*/
|
||||
[self performSelectorOnMainThread: @selector(_populateCache:)
|
||||
withObject: a
|
||||
waitUntilDone: YES
|
||||
modes: queryModes];
|
||||
}
|
||||
result = [c objectForKey: stmt];
|
||||
_lastOperation = GSTickerTimeNow();
|
||||
if (_duration >= 0)
|
||||
{
|
||||
result = toCache = [self backendQuery: stmt recordClass: cls];
|
||||
_lastOperation = GSTickerTimeNow();
|
||||
if (_duration >= 0)
|
||||
{
|
||||
NSTimeInterval d;
|
||||
NSTimeInterval d;
|
||||
|
||||
d = _lastOperation - start;
|
||||
if (d >= _duration)
|
||||
{
|
||||
[self debug: @"Duration %g for query %@", d, stmt];
|
||||
}
|
||||
d = _lastOperation - start;
|
||||
if (d >= _duration)
|
||||
{
|
||||
[self debug: @"Duration %g for query %@", d, stmt];
|
||||
}
|
||||
}
|
||||
|
||||
if (seconds == 0)
|
||||
{
|
||||
// We have been told to remove the existing cached item.
|
||||
[c setObject: nil forKey: stmt lifetime: seconds];
|
||||
toCache = nil;
|
||||
}
|
||||
|
||||
if (toCache != nil)
|
||||
{
|
||||
// We have a newly retrieved object ... cache it.
|
||||
[c setObject: toCache forKey: stmt lifetime: seconds];
|
||||
}
|
||||
|
||||
if (result != nil)
|
||||
{
|
||||
/*
|
||||
* Return an autoreleased copy ... not the original cached data.
|
||||
*/
|
||||
result = [NSMutableArray arrayWithArray: result];
|
||||
}
|
||||
}
|
||||
NS_HANDLER
|
||||
|
||||
if (seconds == 0)
|
||||
{
|
||||
[lock unlock];
|
||||
[localException raise];
|
||||
// We have been told to remove the existing cached item.
|
||||
[c setObject: nil forKey: stmt lifetime: seconds];
|
||||
toCache = nil;
|
||||
}
|
||||
|
||||
if (toCache != nil)
|
||||
{
|
||||
// We have a newly retrieved object ... cache it.
|
||||
[c setObject: toCache forKey: stmt lifetime: seconds];
|
||||
}
|
||||
|
||||
if (result != nil)
|
||||
{
|
||||
/*
|
||||
* Return an autoreleased copy ... not the original cached data.
|
||||
*/
|
||||
result = [NSMutableArray arrayWithArray: result];
|
||||
}
|
||||
NS_ENDHANDLER
|
||||
[lock unlock];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void) setCache: (GSCache*)aCache
|
||||
{
|
||||
[lock lock];
|
||||
if (_cacheThread != nil)
|
||||
{
|
||||
[_cache setDelegate: nil];
|
||||
}
|
||||
ASSIGN(_cache, aCache);
|
||||
if (_cacheThread != nil)
|
||||
{
|
||||
[_cache setDelegate: self];
|
||||
}
|
||||
[lock unlock];
|
||||
}
|
||||
|
||||
- (void) setCacheThread: (NSThread*)aThread
|
||||
{
|
||||
if (mainThread == nil)
|
||||
{
|
||||
[self performSelectorOnMainThread: @selector(_recordMainThread)
|
||||
withObject: nil
|
||||
waitUntilDone: NO
|
||||
modes: queryModes];
|
||||
}
|
||||
if (aThread != nil && aThread != mainThread)
|
||||
{
|
||||
NSLog(@"SQLClient: only the main thread is usable as cache thread");
|
||||
aThread = mainThread;
|
||||
}
|
||||
[lock lock];
|
||||
if (_cacheThread != nil)
|
||||
{
|
||||
[_cache setDelegate: nil];
|
||||
}
|
||||
ASSIGN(_cacheThread, aThread);
|
||||
if (_cacheThread != nil)
|
||||
{
|
||||
[_cache setDelegate: self];
|
||||
}
|
||||
[lock unlock];
|
||||
}
|
||||
@end
|
||||
|
||||
|
|
Loading…
Reference in a new issue