Compare commits

..

15 commits

5 changed files with 590 additions and 373 deletions

View file

@ -1,3 +1,24 @@
2024-01-26 Richard Frith-Macdonald <rfm@gnu.org>
* Postgres.m: Fix error in parsing milliseconds in timestamp.
2023-11-23 Richard Frith-Macdonald <rfm@gnu.org>
* SQLClient.h: New instance variables for timing lock waits.
* SQLClient.m: Record duration of waits for locks.
* SQLClientPool.m: Tell client when pool was locked before query.
Changes so that, the time spent waiting to obtain a lock to get a
connection from a pool or to be able to execute a query on a
connection used by another thread is recorded. When query duration
logging is performed, report lock delays of over a millisecond.
If debug is on or if the query logging duration is zero, then any
lock delay (no matter how small) is reported.
2023-08-16 Richard Frith-Macdonald <rfm@gnu.org>
* Postgres.m: Implement support for connect_timeoout= option to
control how long we allow for a connection attempt to a host.
2023-01-13 Richard Frith-Macdonald <rfm@gnu.org>
* GNUmakefile: bumped version to 1.9.0.

View file

@ -118,7 +118,7 @@ newDateFromBuffer(const char *b, int l)
{
NSCalendarDate *d;
NSTimeZone *zone = nil;
int milliseconds = 0;
int microseconds = 0;
int day;
int month;
int year;
@ -190,14 +190,18 @@ newDateFromBuffer(const char *b, int l)
if (i < l && '.' == b[i])
{
i++;
if (i >= l || !isdigit(b[i])) return nil;
milliseconds = b[i++] - '0';
milliseconds *=- 10;
if (i < l && isdigit(b[i]))
milliseconds += b[i++] - '0';
milliseconds *=- 10;
if (i < l && isdigit(b[i]))
milliseconds += b[i++] - '0';
if (i >= l || !isdigit(b[i])) return nil;
microseconds = b[i++] - '0';
microseconds *= 10;
if (i < l && isdigit(b[i])) microseconds += b[i++] - '0';
microseconds *= 10;
if (i < l && isdigit(b[i])) microseconds += b[i++] - '0';
microseconds *= 10;
if (i < l && isdigit(b[i])) microseconds += b[i++] - '0';
microseconds *= 10;
if (i < l && isdigit(b[i])) microseconds += b[i++] - '0';
microseconds *= 10;
if (i < l && isdigit(b[i])) microseconds += b[i++] - '0';
while (i < l && isdigit(b[i]))
i++;
}
@ -277,15 +281,23 @@ newDateFromBuffer(const char *b, int l)
second: second
timeZone: zone];
if (milliseconds > 0)
/* Postgres support six digits precision, but the ObjC APIs tend
* to use milliseconds. For now, truncate.
*/
if (microseconds > 0)
{
NSTimeInterval ti;
int milliseconds = microseconds / 1000;
ti = milliseconds;
ti /= 1000.0;
ti += [d timeIntervalSinceReferenceDate];
d = [d initWithTimeIntervalSinceReferenceDate: ti];
[d setTimeZone: zone];
if (milliseconds > 0)
{
NSTimeInterval ti;
ti = milliseconds;
ti /= 1000.0;
ti += [d timeIntervalSinceReferenceDate];
d = [d initWithTimeIntervalSinceReferenceDate: ti];
[d setTimeZone: zone];
}
}
}
[d setCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"];
@ -393,7 +405,8 @@ connectQuote(NSString *str)
NSString *host = nil;
NSString *port = nil;
NSString *dbase = [self database];
NSString *sslmode = [options objectForKey: @"sslmode"];
NSString *sslmode;
int timeout;
NSString *str;
NSRange r;
NSRange pwRange = NSMakeRange(NSNotFound, 0);
@ -457,6 +470,8 @@ connectQuote(NSString *str)
[m appendString: @" application_name="];
[m appendString: str];
}
sslmode = [options objectForKey: @"sslmode"];
if ([sslmode isEqual: @"require"])
{
str = connectQuote(@"require");
@ -467,6 +482,12 @@ connectQuote(NSString *str)
}
}
timeout = [[options objectForKey: @"connect_timeout"] intValue];
if (timeout > 0)
{
[m appendFormat: @" connect_timeout=%d", timeout];
}
if ([self debugging] > 0)
{
[self debug: @"Connect to '%@' as %@ (%@)",
@ -1293,8 +1314,24 @@ static inline unsigned int trim(char *str, unsigned len)
if (d > 1)
{
[self debug: @"%@ type:%d mod:%d size: %d\n",
keys[j], ftype[j], fmod[j], size];
#if 0
/* For even more debug we can write some of the
* data retrieved, but that may be a security
* issue.
*/
if (0 == fformat[j] && size <= 100)
{
[self debug:
@"%@ type:%d mod:%d size: %d %*.*s\n",
keys[j], ftype[j], fmod[j], size,
size, size, p];
}
else
#endif
{
[self debug: @"%@ type:%d mod:%d size: %d\n",
keys[j], ftype[j], fmod[j], size];
}
}
/* Often many rows will contain the same data in
* one or more columns, so we check to see if the
@ -1834,7 +1871,8 @@ static inline unsigned int trim(char *str, unsigned len)
type: ET_RDESC
forMode: NSDefaultRunLoopMode
all: YES];
[self debug: @"Listen event on disconnected client, desc: %d", (int)data];
[self debug: @"Listen event on disconnected client, desc: %d",
(int)(intptr_t)data];
}
else
{
@ -1846,7 +1884,7 @@ static inline unsigned int trim(char *str, unsigned len)
strncpy(msg, PQerrorMessage(connection), sizeof(msg)-1);
msg[sizeof(msg)-1] = '\0';
if (PQstatus(connection) != CONNECTION_OK
|| PQsocket(connection) != (int)data)
|| PQsocket(connection) != (int)(intptr_t)data)
{
/* The connection has been lost, so we must disconnect,
* which will stop us receiving events on the descriptor.

View file

@ -521,6 +521,8 @@ SQLCLIENT_PRIVATE
NSTimeInterval _lastOperation;
NSTimeInterval _lastConnect; /** Last successful connect */
NSTimeInterval _lastStart; /** Last op start or connect */
NSTimeInterval _waitLock; /** When we blocked for locking */
NSTimeInterval _waitPool; /** When we blocked for pool access */
NSTimeInterval _duration; /** Duration logging threshold */
uint64_t _committed; /** Count of committed transactions */
unsigned int _debugging; /** The current debugging level */
@ -2254,7 +2256,8 @@ SQLCLIENT_PRIVATE
/** Creates and returns a copy of aString as a literal,
* whether or not aString is already a literal string.
*/
extern SQLLiteral * SQLClientCopyLiteral(NSString *aString);
extern SQLLiteral * SQLClientCopyLiteral(NSString *aString)
NS_RETURNS_RETAINED;
/** Function to test an object to see if it is considered to be a literal
* string by SQLClient. Use this rather than trying to chewck the class
@ -2275,7 +2278,8 @@ extern SQLLiteral * SQLClientMakeLiteral(NSString *aString);
* from a UTF8 or ASCII C string whose length (not including any nul
* terminator) is the specified count.
*/
extern SQLLiteral * SQLClientNewLiteral(const char *bytes, unsigned count);
extern SQLLiteral * SQLClientNewLiteral(const char *bytes, unsigned count)
NS_RETURNS_RETAINED;
/** Creates and returns an autoreleased proxy to aString, recording the
* fact that the programmer considers the string to be a valid literal

View file

@ -326,7 +326,8 @@ SQLClientMakeLiteral(NSString *aString)
if (c != LitStringClass && c != TinyStringClass && c != SQLStringClass)
{
/* The SQLString class uses utf8 and can be very inefficient
* if it's too long. For long strings we use a proxy instead.
* if it's too long. For long strings we use a proxy to a
* copy of the original string.
*/
if ([aString length] > 64)
{
@ -1195,6 +1196,15 @@ static NSArray *rollbackStatement = nil;
@interface SQLClient (Private)
/* Takes the timestamp of the end of the operation and checks how long
* the operation took, returning a string containing a message to be
* logged if the threshold was exceeded.
* The message contains the total duration with a separate breakdown
* of connection and pool lock wait times if they are a millisecond
* or longer.
*/
- (NSMutableString*) _checkDuration: (NSTimeInterval)end;
/**
* Internal method to handle configuration using the notification object.
* This object may be either a configuration front end or a user defaults
@ -1643,14 +1653,6 @@ static int poolConnections = 0;
{
NSNotificationCenter *nc;
[clientsLock lock];
NSHashRemove(clientsHash, (void*)self);
if (_name != nil
&& (SQLClient*)NSMapGet(clientsMap, (void*)_name) == self)
{
NSMapRemove(clientsMap, (void*)_name);
}
[clientsLock unlock];
nc = [NSNotificationCenter defaultCenter];
[nc removeObserver: self];
if (YES == connected) [self disconnect];
@ -2576,14 +2578,21 @@ static int poolConnections = 0;
* from grabbing this object while we are checking it.
*/
[clientsLock lock];
if (nil != _pool && [self retainCount] == 1)
if ([self retainCount] > 1)
{
[super release];
[clientsLock unlock];
return;
}
if (_pool)
{
/* This is the only reference to a client associated with
* a connection pool we put this client back to the pool.
*
* That being the case, we know that this thread 'owns'
* the client and it's not going to be deallocated and not
* going to have the _pool iinstance variable changed, so it
* going to have the _pool instance variable changed, so it
* is safe to unlock clientsLock before returning the client
* to the pool. This avoids a possible deadlock when a pool
* is being purged.
@ -2600,8 +2609,18 @@ static int poolConnections = 0;
}
else
{
[super release];
/* As we are going to deallocate the object, we first remove
* it from global tables so that no other thread will find it
* and try to use it while it is being deallocated.
*/
NSHashRemove(clientsHash, (void*)self);
if (_name != nil
&& (SQLClient*)NSMapGet(clientsMap, (void*)_name) == self)
{
NSMapRemove(clientsMap, (void*)_name);
}
[clientsLock unlock];
[super release];
}
}
@ -2795,12 +2814,13 @@ static int poolConnections = 0;
- (NSInteger) simpleExecute: (id)info
{
NSInteger result;
NSString *debug = nil;
BOOL done = NO;
BOOL isCommit = NO;
BOOL isRollback = NO;
NSString *statement;
NSInteger result;
NSString *debug = nil;
BOOL done = NO;
BOOL isCommit = NO;
BOOL isRollback = NO;
NSString *statement;
NSTimeInterval wait = 0.0;
if ([info isKindOfClass: NSArrayClass] == NO)
{
@ -2815,7 +2835,12 @@ static int poolConnections = 0;
info = [NSMutableArray arrayWithObject: info];
}
[lock lock];
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
statement = [info objectAtIndex: 0];
@ -2823,6 +2848,8 @@ static int poolConnections = 0;
*/
if ([self connect] == NO)
{
_waitPool = 0.0;
_waitLock = 0.0;
[lock unlock];
[NSException raise: SQLConnectionException
format: @"Unable to connect to '%@' to run statement %@",
@ -2844,60 +2871,51 @@ static int poolConnections = 0;
done = YES;
NS_DURING
{
NSMutableString *m;
_lastStart = GSTickerTimeNow();
result = [self backendExecute: info];
_lastOperation = GSTickerTimeNow();
[_statements addObject: statement];
if (_duration >= 0)
m = [self _checkDuration: _lastOperation];
if (m)
{
NSTimeInterval d;
if (isCommit || isRollback)
{
NSEnumerator *e = [_statements objectEnumerator];
d = _lastOperation - _lastStart;
if (d >= _duration)
{
NSMutableString *m;
if (isCommit || isRollback)
{
NSEnumerator *e = [_statements objectEnumerator];
if (isCommit)
{
m = [NSMutableString stringWithFormat:
@"Duration %g for transaction commit ...\n", d];
}
else
{
m = [NSMutableString stringWithFormat:
@"Duration %g for transaction rollback ...\n", d];
}
while ((statement = [e nextObject]) != nil)
{
[m appendFormat: @" %@;\n", statement];
}
[m appendFormat: @" affected %"PRIdPTR" record%s\n",
result, ((1 == result) ? "" : "s")];
}
else if ([self debugging] > 1)
{
/*
* For higher debug levels, we log data objects as well
* as the query string, otherwise we omit them.
*/
m = [NSMutableString stringWithFormat:
@"Duration %g for statement %@;", d, info];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
else
{
m = [NSMutableString stringWithFormat:
@"Duration %g for statement %@;", d, statement];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
debug = m;
}
if (isCommit)
{
[m appendString: @" for transaction commit ...\n"];
}
else
{
[m appendString: @" for transaction rollback ...\n"];
}
while ((statement = [e nextObject]) != nil)
{
[m appendFormat: @" %@;\n", statement];
}
[m appendFormat: @" affected %"PRIdPTR" record%s\n",
result, ((1 == result) ? "" : "s")];
}
else if ([self debugging] > 1)
{
/*
* For higher debug levels, we log data objects as well
* as the query string, otherwise we omit them.
*/
[m appendFormat: @" for statement %@;", info];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
else
{
[m appendFormat: @" for statement %@;", statement];
[m appendFormat: @" affected %"PRIdPTR" record%s",
result, ((1 == result) ? "" : "s")];
}
debug = m;
}
if (_inTransaction == NO)
{
@ -2952,10 +2970,17 @@ static int poolConnections = 0;
NSMutableArray *result = nil;
NSString *debug = nil;
BOOL done = NO;
NSTimeInterval wait = 0.0;
if (rtype == 0) rtype = rClass;
if (ltype == 0) ltype = aClass;
[lock lock];
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
if ([self connect] == NO)
{
[lock unlock];
@ -2968,22 +2993,19 @@ static int poolConnections = 0;
done = YES;
NS_DURING
{
NSMutableString *m;
_lastStart = GSTickerTimeNow();
result = [self backendQuery: stmt recordType: rtype listType: ltype];
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
m = [self _checkDuration: _lastOperation];
if (m)
{
NSTimeInterval d;
NSUInteger count = [result count];
d = _lastOperation - _lastStart;
if (d >= _duration)
{
NSUInteger count = [result count];
debug = [NSString stringWithFormat:
@"Duration %g for query %@; produced %"PRIuPTR" record%s",
d, stmt, count, ((1 == count) ? "" : "s")];
}
[m appendFormat: @" for query %@; produced %"PRIuPTR" record%s",
stmt, count, ((1 == count) ? "" : "s")];
debug = m;
}
if (_inTransaction == NO)
{
@ -3027,8 +3049,25 @@ static int poolConnections = 0;
{
if (NO == connected)
{
[lock lock];
if (NO == connected)
NSTimeInterval wait = 0.0;
NSString *msg;
if (NO == [lock tryLock])
{
wait = GSTickerTimeNow();
[lock lock];
}
_waitLock = wait;
_lastStart = GSTickerTimeNow();
if (connected)
{
msg = [self _checkDuration: _lastStart];
if (msg)
{
[self debug: @"%@ for existing connection.", msg];
}
}
else
{
NS_DURING
{
@ -3052,7 +3091,6 @@ static int poolConnections = 0;
}
}
_lastStart = GSTickerTimeNow();
if (YES == [self backendConnect])
{
/* On establishing a new connection, we must restore any
@ -3071,43 +3109,39 @@ static int poolConnections = 0;
}
}
_lastConnect = GSTickerTimeNow();
msg = [self _checkDuration: _lastConnect];
_connectFails = 0;
}
else
{
_lastOperation = GSTickerTimeNow();
msg = [self _checkDuration: _lastOperation];
_connectFails++;
}
if (_duration >= 0)
if (msg)
{
NSTimeInterval d;
NSString *s;
NSString *s;
if (0 == _connectFails)
{
s = @"success";
d = _lastConnect - _lastStart;
}
else
{
s = @"failure";
d = _lastOperation - _lastStart;
}
if (d >= _duration)
if (_lastListen > 0.0)
{
if (_lastListen > 0.0)
{
[self debug: @"Duration %g for connection (%@)"
@", of which %g adding observers.",
d, s, _lastOperation - _lastListen];
}
else
{
[self debug: @"Duration %g for connection (%@).",
d, s];
}
[self debug: @"%@ for connection (%@)"
@", of which %g adding observers.",
msg, s, _lastOperation - _lastListen];
}
else
{
[self debug: @"%@ for connection (%@).",
msg, s];
}
}
}
@ -3222,6 +3256,7 @@ static int poolConnections = 0;
unsigned char *buf;
unsigned char *ptr;
const unsigned char *from = (const unsigned char*)statement;
const unsigned char *end = from + strlen((const char*)statement);
/*
* Calculate length of buffer needed.
@ -3240,10 +3275,10 @@ static int poolConnections = 0;
* Merge quoted data objects into statement.
*/
i = 1;
from = (unsigned char*)statement;
while (*from != 0)
while (from < end)
{
if (*from == *(unsigned char*)marker
&& (from + mLength) < end
&& memcmp(from, marker, mLength) == 0)
{
NSData *d = [blobs objectAtIndex: i++];
@ -3274,6 +3309,80 @@ static int poolConnections = 0;
@implementation SQLClient (Private)
- (NSMutableString*) _checkDuration: (NSTimeInterval)end
{
NSMutableString *m = nil;
NSTimeInterval ti = _lastStart;
if (_waitLock > 0.0 && _waitLock < ti)
{
ti = _waitLock;
}
if (_waitPool > 0.0 && _waitPool < ti)
{
ti = _waitPool;
}
ti = end - ti; // Total duration
if (ti > _duration)
{
BOOL additional = NO;
NSTimeInterval threshold = 0.001;
/* The threshold for reporting lock information is set to zero if
* we have debug turned on or if the logging threshold is set to
* zero (to log every query). A level of zero means that any
* locjk which was not obtained immediately is recorded.
*/
if (_duration <= 0.0 || _debugging > 0.0)
{
threshold = 0.0;
}
m = [NSMutableString stringWithCapacity: 1000];
[m appendFormat: @"Duration %g", ti];
if (_waitPool > 0.0)
{
if (_waitLock > 0.0)
{
ti = _waitLock - _waitPool;
}
else
{
ti = _lastStart - _waitPool;
}
if (ti >= threshold)
{
[m appendFormat: @" (%g waiting for connection pool", ti];
additional = YES;
}
}
if (_waitLock > 0.0)
{
ti = _lastStart - _waitLock;
if (ti >= threshold)
{
if (additional)
{
[m appendFormat: @", %g waiting for connection lock", ti];
}
else
{
[m appendFormat: @" (%g waiting for connection lock", ti];
}
additional = YES;
}
}
if (additional)
{
[m appendString: @")"];
}
}
_waitPool = 0.0;
_waitLock = 0.0;
return m;
}
- (void) _configure: (NSNotification*)n
{
NSDictionary *o;
@ -3746,6 +3855,8 @@ static int poolConnections = 0;
GSCache *c;
id toCache;
BOOL cacheHit;
NSTimeInterval start;
NSString *s;
if (rtype == 0) rtype = rClass;
if (ltype == 0) ltype = aClass;
@ -3753,7 +3864,7 @@ static int poolConnections = 0;
md = [[NSThread currentThread] threadDictionary];
[md setObject: rtype forKey: @"SQLClientRecordType"];
[md setObject: ltype forKey: @"SQLClientListType"];
_lastStart = GSTickerTimeNow();
start = GSTickerTimeNow();
c = [self cache];
toCache = nil;
@ -3821,17 +3932,12 @@ static int poolConnections = 0;
result = [[result mutableCopy] autorelease];
}
_lastStart = start;
_lastOperation = GSTickerTimeNow();
if (_duration >= 0)
if ((s = [self _checkDuration: _lastOperation]) != nil)
{
NSTimeInterval d;
d = _lastOperation - _lastStart;
if (d >= _duration)
{
[self debug: @"Duration %g for cache-%@ query %@",
d, (YES == cacheHit) ? @"hit" : @"miss", stmt];
}
[self debug: @"%@ for cache-%@ query %@",
s, (YES == cacheHit) ? @"hit" : @"miss", stmt];
}
return result;
}
@ -5238,6 +5344,11 @@ nextUTF8(const uint8_t *p, unsigned l, unsigned *o, unichar *n)
NS_ENDHANDLER
}
- (id) copyWithZone: (NSZone*)z
{
return RETAIN(self);
}
- (NSData*) dataUsingEncoding: (NSStringEncoding)encoding
allowLossyConversion: (BOOL)flag
{
@ -5484,11 +5595,6 @@ nextUTF8(const uint8_t *p, unsigned l, unsigned *o, unichar *n)
return range;
}
- (id) copyWithZone: (NSZone*)z
{
return RETAIN(self);
}
- (NSZone*) zone
{
return NSDefaultMallocZone();

View file

@ -48,6 +48,7 @@ struct _SQLClientPoolItem {
@interface SQLClient(Pool)
- (void) _clearPool: (SQLClientPool*)p;
- (void) _waitPool: (NSTimeInterval)ti;
@end
@implementation SQLClient(Pool)
@ -57,6 +58,10 @@ struct _SQLClientPoolItem {
_pool = nil;
}
- (void) _waitPool: (NSTimeInterval)ti
{
_waitPool = ti;
}
@end
@interface SQLClientPool (Adjust)
@ -65,6 +70,10 @@ struct _SQLClientPoolItem {
@interface SQLClientPool (Private)
- (void) _lock;
- (SQLClient*) _provideClientBeforeDate: (NSDate*)when
exclusive: (BOOL)isLocal
blocked: (NSTimeInterval*)ti;
- (SQLClient*) _provide;
- (NSString*) _rc: (SQLClient*)o;
- (void) _unlock;
@end
@ -239,223 +248,9 @@ static Class cls = Nil;
- (SQLClient*) provideClientBeforeDate: (NSDate*)when exclusive: (BOOL)isLocal
{
NSThread *thread = [NSThread currentThread];
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval now = start;
SQLClient *client = nil;
int preferred = -1;
int found = -1;
int cond = 0;
int index;
/* If this is a request for a non-exclusive connection, we can simply
* check to see if there's already such a connection available.
*/
if (NO == isLocal)
{
[_lock lock];
for (index = 0; index < _max; index++)
{
if (_items[index].o == thread && _items[index].u < NSNotFound
&& NO == [_items[index].c isInTransaction])
{
preferred = index; // Ignore any other connected client
break;
}
if (nil == _items[index].o && 0 == _items[index].u)
{
if (preferred < 0 && YES == [_items[index].c connected])
{
preferred = index;
}
else
{
found = index;
}
}
}
if (preferred >= 0)
{
found = preferred; // Prefer a connected client.
}
if (found >= 0)
{
_items[found].t = now;
if (0 == _items[found].u++)
{
ASSIGN(_items[found].o, thread);
client = [_items[found].c autorelease];
}
else
{
/* We have already provided this client, so we must retain it
* before we autorelease it, to keep retain counts in sync.
*/
client = [[_items[found].c retain] autorelease];
}
_immediate++;
}
[self _unlock];
if (nil != client)
{
if (_debugging > 2)
{
NSLog(@"%@ provides %p%@",
self, _items[found].c, [self _rc: client]);
}
return client;
}
}
/* If we haven't been given a timeout, we should wait for a client
* indefinitely ... so we set the timeout to be in the distant future.
*/
if (nil == when)
{
static NSDate *future = nil;
if (nil == future)
{
future = RETAIN([NSDate distantFuture]);
}
when = future;
}
/* We want to log stuff if we don't get a client quickly.
* Ideally we get the lock straight away,
* but if not we want to log every ten seconds (and possibly
* when we begin waiting).
*/
if (YES == [_lock tryLockWhenCondition: 1])
{
_immediate++;
}
else
{
NSTimeInterval end = [when timeIntervalSinceReferenceDate];
NSTimeInterval dif = 0.0;
NSDate *until;
BOOL locked;
if (_debugging > 1)
{
NSLog(@"%@ has no clients available", self);
}
until = [[NSDate alloc]
initWithTimeIntervalSinceReferenceDate: now + 10.0];
locked = NO;
while (NO == locked && now < end)
{
if (now >= end)
{
/* End date is passed ... try to get the lock immediately.
*/
locked = [_lock tryLockWhenCondition: 1];
}
else if ([when earlierDate: until] == until)
{
locked = [_lock lockWhenCondition: 1 beforeDate: until];
}
else
{
locked = [_lock lockWhenCondition: 1 beforeDate: when];
}
now = [NSDate timeIntervalSinceReferenceDate];
dif = now - start;
if (NO == locked && now < end)
{
if (_debugging > 0 || dif > 30.0
|| (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ still waiting after %g seconds:\n%@",
self, dif, [self status]);
}
[until release];
until = [[NSDate alloc] initWithTimeIntervalSinceNow: 10.0];
}
}
[until release];
if (dif > _longest)
{
_longest = dif;
}
if (NO == locked)
{
if (_debugging > 0 || dif > 30.0
|| (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ abandoned wait after %g seconds:\n%@",
self, dif, [self status]);
}
_failed++;
_failWaits += dif;
return nil;
}
if (_debugging > 0 || (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ provided client after %g seconds",
self, dif);
}
_delayed++;
_delayWaits += dif;
}
for (index = 0; index < _max && 0 == cond; index++)
{
if (0 == _items[index].u)
{
if (preferred >= 0 || found >= 0)
{
/* There's at least one more client available to be
* provided, so we want to re-lock with condition 1.
*/
cond = 1;
}
if (preferred < 0 && YES == [_items[index].c connected])
{
preferred = index;
}
else
{
found = index;
}
}
else if (NO == isLocal
&& _items[index].o == thread
&& _items[index].u < NSNotFound
&& NO == [_items[index].c isInTransaction])
{
/* We are allowed to re-use connections in the current thread,
* so if we have found one, treat it as the preferred choice.
*/
preferred = index;
}
}
/* We prefer to use a client which is already connected, so we
* avoid opening unnecessary connections.
*/
if (preferred >= 0)
{
found = preferred;
}
if (YES == isLocal)
{
_items[found].u = NSNotFound;
}
else
{
_items[found].u++;
}
_items[found].t = now;
ASSIGN(_items[found].o, thread);
[self _unlock];
client = [_items[found].c autorelease];
if (_debugging > 2)
{
NSLog(@"%@ provides %p%@", self, _items[found].c, [self _rc: client]);
}
return client;
return [self _provideClientBeforeDate: when
exclusive: isLocal
blocked: (NSTimeInterval*)NULL];
}
- (SQLClient*) provideClientExclusive
@ -703,6 +498,7 @@ static Class cls = Nil;
for (index = 0; index < _max; index++)
{
SQLClient *client = _items[index].c;
NSThread *owner = _items[index].o;
NSUInteger rc = [client retainCount];
NSUInteger uc = _items[index].u;
@ -716,15 +512,15 @@ static Class cls = Nil;
if (NSNotFound == uc)
{
tmp = [NSString stringWithFormat: @" Client '%@'"
@" provided exclusively (retained:%"PRIuPTR,
[client name], rc];
@" provided to %@ exclusively (retained:%"PRIuPTR,
[client name], owner, rc];
}
else
{
tmp = [NSString stringWithFormat: @" Client '%@'"
@" provided %"PRIuPTR
@" provided to %@ %"PRIuPTR
@" time%s (retained:%"PRIuPTR,
[client name], uc, ((1 == uc) ? "" : "s"), rc];
[client name], owner, uc, ((1 == uc) ? "" : "s"), rc];
}
#if defined(GNUSTEP)
@ -970,6 +766,258 @@ static Class cls = Nil;
[_lock lock];
}
- (SQLClient*) _provideClientBeforeDate: (NSDate*)when
exclusive: (BOOL)isLocal
blocked: (NSTimeInterval*)ti
{
NSThread *thread = [NSThread currentThread];
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval now = start;
NSTimeInterval block = 0.0;
NSTimeInterval dif = 0.0;
SQLClient *client = nil;
int preferred = -1;
int found = -1;
int cond = 0;
int index;
/* If this is a request for a non-exclusive connection, we can simply
* check to see if there's already such a connection available.
*/
if (NO == isLocal)
{
if (NO == [_lock tryLock])
{
[_lock lock];
block = start;
}
for (index = 0; index < _max; index++)
{
if (_items[index].o == thread && _items[index].u < NSNotFound
&& NO == [_items[index].c isInTransaction])
{
preferred = index; // Ignore any other connected client
break;
}
if (nil == _items[index].o && 0 == _items[index].u)
{
if (preferred < 0 && YES == [_items[index].c connected])
{
preferred = index;
}
else
{
found = index;
}
}
}
if (preferred >= 0)
{
found = preferred; // Prefer a connected client.
}
if (found >= 0)
{
_items[found].t = now;
if (0 == _items[found].u++)
{
ASSIGN(_items[found].o, thread);
client = [_items[found].c autorelease];
}
else
{
/* We have already provided this client, so we must retain it
* before we autorelease it, to keep retain counts in sync.
*/
client = [[_items[found].c retain] autorelease];
}
_immediate++;
}
[self _unlock];
if (nil != client)
{
if (_debugging > 2)
{
NSLog(@"%@ provides %p%@",
self, _items[found].c, [self _rc: client]);
}
if (ti)
{
*ti = block;
}
return client;
}
}
/* If we haven't been given a timeout, we should wait for a client
* indefinitely ... so we set the timeout to be in the distant future.
*/
if (nil == when)
{
static NSDate *future = nil;
if (nil == future)
{
future = RETAIN([NSDate distantFuture]);
}
when = future;
}
/* We want to log stuff if we don't get a client quickly.
* Ideally we get the lock straight away,
* but if not we want to log every ten seconds (and possibly
* when we begin waiting).
*/
if ([_lock tryLockWhenCondition: 1])
{
_immediate++;
}
else
{
NSTimeInterval end = [when timeIntervalSinceReferenceDate];
NSDate *until;
BOOL locked;
block = start;
if (_debugging > 1)
{
NSLog(@"%@ has no clients available", self);
}
until = [[NSDate alloc]
initWithTimeIntervalSinceReferenceDate: now + 10.0];
locked = NO;
while (NO == locked && now < end)
{
if (now >= end)
{
/* End date is passed ... try to get the lock immediately.
*/
locked = [_lock tryLockWhenCondition: 1];
}
else if ([when earlierDate: until] != when)
{
locked = [_lock lockWhenCondition: 1 beforeDate: until];
}
else
{
locked = [_lock lockWhenCondition: 1 beforeDate: when];
}
now = [NSDate timeIntervalSinceReferenceDate];
dif = now - start;
if (NO == locked && now < end)
{
if (_debugging > 0 || dif > 30.0
|| (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ still waiting after %g seconds:\n%@%@\n",
self, dif, [self status], [NSThread callStackSymbols]);
}
[until release];
until = [[NSDate alloc] initWithTimeIntervalSinceNow: 10.0];
}
}
[until release];
if (dif > _longest)
{
_longest = dif;
}
if (NO == locked)
{
if (_debugging > 0 || dif > 30.0
|| (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ abandoned wait after %g seconds:\n%@",
self, dif, [self status]);
}
_failed++;
_failWaits += dif;
if (ti)
{
*ti = block;
}
return nil;
}
_delayed++;
_delayWaits += dif;
}
for (index = 0; index < _max && 0 == cond; index++)
{
if (0 == _items[index].u)
{
if (preferred >= 0 || found >= 0)
{
/* There's at least one more client available to be
* provided, so we want to re-lock with condition 1.
*/
cond = 1;
}
if (preferred < 0 && YES == [_items[index].c connected])
{
preferred = index;
}
else
{
found = index;
}
}
else if (NO == isLocal
&& _items[index].o == thread
&& _items[index].u < NSNotFound
&& NO == [_items[index].c isInTransaction])
{
/* We are allowed to re-use connections in the current thread,
* so if we have found one, treat it as the preferred choice.
*/
preferred = index;
}
}
/* We prefer to use a client which is already connected, so we
* avoid opening unnecessary connections.
*/
if (preferred >= 0)
{
found = preferred;
}
if (YES == isLocal)
{
_items[found].u = NSNotFound;
}
else
{
_items[found].u++;
}
_items[found].t = now;
ASSIGN(_items[found].o, thread);
[self _unlock];
client = [_items[found].c autorelease];
if (_debugging > 0 || (_duration >= 0.0 && dif > _duration))
{
NSLog(@"%@ provided client %p after %g seconds", self, client, dif);
}
if (_debugging > 2)
{
NSLog(@"%@ provides %p %@", self, client, [self _rc: client]);
}
if (ti)
{
*ti = block;
}
return client;
}
- (SQLClient*) _provide
{
NSTimeInterval start;
SQLClient *db;
db = [self _provideClientBeforeDate: nil
exclusive: NO
blocked: &start];
[db _waitPool: start];
return db;
}
- (NSString*) _rc: (SQLClient*)o
{
#if defined(GNUSTEP)
@ -1083,7 +1131,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds simpleQuery: query];
NS_HANDLER
@ -1101,7 +1149,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds query: stmt with: values];
NS_HANDLER
@ -1117,7 +1165,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds simpleQuery: stmt];
NS_HANDLER
@ -1136,7 +1184,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db cache: seconds
simpleQuery: stmt
@ -1165,7 +1213,7 @@ static Class cls = Nil;
va_start (ap, stmt);
info = [_items[0].c prepare: stmt args: ap];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleExecute: info];
NS_HANDLER
@ -1181,7 +1229,7 @@ static Class cls = Nil;
SQLClient *db;
NSInteger result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db execute: stmt with: values];
NS_HANDLER
@ -1233,7 +1281,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1250,7 +1298,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db query: stmt with: values];
NS_HANDLER
@ -1273,7 +1321,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1308,7 +1356,7 @@ static Class cls = Nil;
query = [[_items[0].c prepare: stmt args: ap] objectAtIndex: 0];
va_end (ap);
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: query];
NS_HANDLER
@ -1422,7 +1470,7 @@ static Class cls = Nil;
SQLClient *db;
NSInteger result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleExecute: info];
NS_HANDLER
@ -1438,7 +1486,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: stmt];
NS_HANDLER
@ -1456,7 +1504,7 @@ static Class cls = Nil;
SQLClient *db;
NSMutableArray *result;
db = [self provideClient];
db = [self _provide];
NS_DURING
result = [db simpleQuery: stmt
recordType: rtype