Support cache refresh via delegate.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@33253 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2011-06-06 08:25:49 +00:00
parent dde5501c2b
commit 71d2b71d96
3 changed files with 127 additions and 9 deletions

View file

@ -1,3 +1,13 @@
2011-06-06 Richard Frith-Macdonald <rfm@gnu.org>
* GSCache.h:
* GSCache.m:
Add a new method to let the delegate know when a cache hit has
occurred on an item which is nearning the end of its lifetime ...
providing an opportunity for the delegate to refresh the cache
before the actual expiry.
Also fix minor memory leak.
2011-06-05 Richard Frith-Macdonald <rfm@gnu.org>
* GSFIFO.h:

View file

@ -132,8 +132,9 @@
/**
* Sets the delegate for the receiver.<br />
* The delegate object is not retained.<br />
* If a delegate it set, it must implement the methods in the
* (GSCacheDelegate) protocol.
* If a delegate it set, it will be sent the messages in the
* (GSCacheDelegate) protocol (if it implements them ... which
* it does not need to do).
*/
- (void) setDelegate: (id)anObject;
@ -213,10 +214,28 @@
/**
* This protocol defines the messages which may be sent to a delegate
* of a GSCache object.
* of a GSCache object. The messages are only sent if the delegate
* actually implements them, so a delegate does not need to actually
* conform to the protocol.
*/
@protocol GSCacheDelegate
/**
* Alerts the delegate to the fact that anObject, which was cached
* using aKey and will expire delay seconds in the future has been
* looked up now, and needs to be refreshed if it is not to expire
* from the cache.<br />
* This is called the first time an attempt is made to access the
* cached value for aKey and the object is found in the cache but
* more than half its lifetime has expired.<br />
* The delegate method (if implemented) may replace the item in the
* cache immediately, or do it later asynchronously, or may simply
* take no action.
*/
- (void) mayRefreshItem: (id)anObject
withKey: (id)aKey
lifetime: (unsigned)lifetime
after: (unsigned)delay;
/**
* Asks the delegate to decide whether anObject, which was cached
* using aKey and expired delay seconds ago should still be retained

101
GSCache.m
View file

@ -64,6 +64,7 @@
GSCacheItem *next;
GSCacheItem *prev;
unsigned life;
unsigned warn;
unsigned when;
unsigned size;
id key;
@ -98,6 +99,8 @@ static NSLock *allCachesLock = nil;
typedef struct {
id delegate;
void (*refresh)(id, SEL, id, id, unsigned, unsigned);
BOOL (*replace)(id, SEL, id, id, unsigned, unsigned);
unsigned currentObjects;
unsigned currentSize;
unsigned lifetime;
@ -339,15 +342,26 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
{
BOOL keep = NO;
if (my->delegate != nil)
if (0 != my->replace)
{
GSCacheItem *orig = [item retain];
[my->lock unlock];
keep = [my->delegate shouldKeepItem: item->object
withKey: aKey
lifetime: item->life
after: when - item->when];
NS_DURING
{
keep = (*(my->replace))(my->delegate,
@selector(shouldKeepItem:withKey:lifetime:after:),
item->object,
aKey,
item->life,
when - item->when);
}
NS_HANDLER
{
[my->lock unlock];
[localException raise];
}
NS_ENDHANDLER
[my->lock lock];
if (keep == YES)
{
@ -364,6 +378,7 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
*/
my->misses++;
[my->lock unlock];
[orig release];
return nil;
}
else if (orig == current)
@ -372,6 +387,7 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
* update its expiry time.
*/
item->when = when + item->life;
item->warn = when + item->life / 2;
}
else
{
@ -398,6 +414,52 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
return nil; // Lifetime expired.
}
}
else if (item->warn > 0 && item->warn < when)
{
item->warn = 0; // Don't warn again.
if (0 != my->refresh)
{
GSCacheItem *orig = [item retain];
GSCacheItem *current;
[my->lock unlock];
NS_DURING
{
(*(my->refresh))(my->delegate,
@selector(mayRefreshItem:withKey:lifetime:after:),
item->object,
aKey,
item->life,
when - item->when);
}
NS_HANDLER
{
[my->lock unlock];
[localException raise];
}
NS_ENDHANDLER
[my->lock lock];
/* Refetch in case delegate changed it.
*/
current = (GSCacheItem*)NSMapGet(my->contents, aKey);
if (current == nil)
{
/* Delegate must have deleted the item!
* So we count this as a miss.
*/
my->misses++;
[my->lock unlock];
[orig release];
return nil;
}
else
{
item = current;
}
[orig release];
}
}
// Least recently used ... move to end of list.
removeItem(item, &my->first);
@ -461,7 +523,31 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
- (void) setDelegate: (id)anObject
{
[my->lock lock];
my->delegate = anObject;
if ([my->delegate respondsToSelector:
@selector(shouldKeepItem:withKey:lifetime:after:)])
{
my->replace = (BOOL (*)(id,SEL,id,id,unsigned,unsigned))
[my->delegate methodForSelector:
@selector(shouldKeepItem:withKey:lifetime:after:)];
}
else
{
my->replace = 0;
}
if ([my->delegate respondsToSelector:
@selector(mayRefreshItem:withKey:lifetime:after:)])
{
my->refresh = (void (*)(id,SEL,id,id,unsigned,unsigned))
[my->delegate methodForSelector:
@selector(mayRefreshItem:withKey:lifetime:after:)];
}
else
{
my->refresh = 0;
}
[my->lock unlock];
}
- (void) setLifetime: (unsigned)max
@ -597,7 +683,10 @@ static void removeItem(GSCacheItem *item, GSCacheItem **first)
item = [GSCacheItem newWithObject: anObject forKey: aKey];
if (lifetime > 0)
{
item->when = GSTickerTimeTick() + lifetime;
unsigned tick = GSTickerTimeTick();
item->when = tick + lifetime;
item->warn = tick + lifetime / 2;
}
item->life = lifetime;
item->size = addSize;