Skip to content

Commit 8075aeb

Browse files
epateyMallory Paine
authored andcommitted
Pull request: Fix MRU eviction bugs that render the wrong contents for images
Edit ------ Introduce the concept of in use entries. Entries that are in use are immune from eviction. This concept is needed to protect the bits that are currently being referenced by a UIImage/CGImageRef from being overwritten. Note also that inUse is a refcounted concept since multiple FICImageTableEntry's can be allocated against the same entityUUID/bits in the backing file. A side effect of this change is that the imageFormat's maximumCount is no longer a hard limit since it is possible that all maximumCount entries could be in use.
2 parents cedc32d + 0dabf60 commit 8075aeb

3 files changed

Lines changed: 66 additions & 6 deletions

File tree

FastImageCache/FICImageTable.m

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ @interface FICImageTable () {
5959
NSMutableDictionary *_sourceImageMap; // Key: entity UUID, value: source image UUID
6060
NSMutableIndexSet *_occupiedIndexes;
6161
NSMutableOrderedSet *_MRUEntries;
62+
NSCountedSet *_inUseEntries;
6263
NSDictionary *_imageFormatDictionary;
6364
}
6465

@@ -144,6 +145,8 @@ - (instancetype)initWithFormat:(FICImageFormat *)imageFormat {
144145
_occupiedIndexes = [[NSMutableIndexSet alloc] init];
145146

146147
_MRUEntries = [[NSMutableOrderedSet alloc] init];
148+
_inUseEntries = [NSCountedSet set];
149+
147150
_sourceImageMap = [[NSMutableDictionary alloc] init];
148151

149152
_recentChunks = [[NSMutableArray alloc] init];
@@ -163,6 +166,10 @@ - (instancetype)initWithFormat:(FICImageFormat *)imageFormat {
163166
NSInteger goalChunkLength = 2 * (1024 * 1024);
164167
NSInteger goalEntriesPerChunk = goalChunkLength / _entryLength;
165168
_entriesPerChunk = MAX(4, goalEntriesPerChunk);
169+
if ([self _maximumCount] > [_imageFormat maximumCount]) {
170+
NSString *message = [NSString stringWithFormat:@"*** FIC Warning: growing desired maximumCount (%d) for format %@ to fill a chunk (%d)", [_imageFormat maximumCount], [_imageFormat name], [self _maximumCount]];
171+
[[FICImageCache sharedImageCache] _logMessage:message];
172+
}
166173
_chunkLength = (size_t)(_entryLength * _entriesPerChunk);
167174

168175
_fileLength = lseek(_fileDescriptor, 0, SEEK_END);
@@ -263,8 +270,7 @@ - (void)setEntryForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSString *
263270
newEntryIndex = [self _nextEntryIndex];
264271

265272
if (newEntryIndex >= _entryCount) {
266-
NSInteger maximumEntryCount = [_imageFormat maximumCount];
267-
NSInteger newEntryCount = MIN(maximumEntryCount, _entryCount + MAX(_entriesPerChunk, newEntryIndex - _entryCount + 1));
273+
NSInteger newEntryCount = _entryCount + MAX(_entriesPerChunk, newEntryIndex - _entryCount + 1);
268274
[self _setEntryCount:newEntryCount];
269275
}
270276
}
@@ -330,6 +336,12 @@ - (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSStr
330336
// Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way.
331337
CGDataProviderRef dataProvider = CGDataProviderCreateWithData((__bridge_retained void *)entryData, [entryData bytes], [entryData imageLength], _FICReleaseImageData);
332338

339+
[_inUseEntries addObject:entityUUID];
340+
__weak FICImageTable *weakSelf = self;
341+
[entryData executeBlockOnDealloc:^{
342+
[weakSelf removeInUseForEntityUUID:entityUUID];
343+
}];
344+
333345
CGSize pixelSize = [_imageFormat pixelSize];
334346
CGBitmapInfo bitmapInfo = [_imageFormat bitmapInfo];
335347
NSInteger bitsPerComponent = [_imageFormat bitsPerComponent];
@@ -357,7 +369,15 @@ - (UIImage *)newImageForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSStr
357369
}
358370

359371
static void _FICReleaseImageData(void *info, const void *data, size_t size) {
360-
CFRelease(info);
372+
if (info) {
373+
CFRelease(info);
374+
}
375+
}
376+
377+
- (void)removeInUseForEntityUUID:(NSString *)entityUUID {
378+
[_lock lock];
379+
[_inUseEntries removeObject:entityUUID];
380+
[_lock unlock];
361381
}
362382

363383
- (void)deleteEntryForEntityUUID:(NSString *)entityUUID {
@@ -411,6 +431,10 @@ - (BOOL)entryExistsForEntityUUID:(NSString *)entityUUID sourceImageUUID:(NSStrin
411431

412432
#pragma mark - Working with Entries
413433

434+
- (int)_maximumCount {
435+
return MAX([_imageFormat maximumCount], _entriesPerChunk);
436+
}
437+
414438
- (void)_setEntryCount:(NSInteger)entryCount {
415439
if (entryCount != _entryCount) {
416440
off_t fileLength = entryCount * _entryLength;
@@ -460,15 +484,37 @@ - (NSInteger)_nextEntryIndex {
460484
index = _entryCount;
461485
}
462486

463-
if (index >= [_imageFormat maximumCount] && [_MRUEntries count]) {
487+
if (index >= [self _maximumCount] && [_MRUEntries count]) {
464488
// Evict the oldest/least-recently accessed entry here
465-
[self deleteEntryForEntityUUID:[_MRUEntries lastObject]];
466-
index = [self _nextEntryIndex];
489+
490+
NSString *oldestEvictableEntityUUID = [self oldestEvictableEntityUUID];
491+
if (oldestEvictableEntityUUID) {
492+
[self deleteEntryForEntityUUID:oldestEvictableEntityUUID];
493+
index = [self _nextEntryIndex];
494+
}
495+
}
496+
497+
if (index >= [self _maximumCount]) {
498+
NSString *message = [NSString stringWithFormat:@"FICImageTable - unable to evict entry from table '%@' to make room. New index %d, desired max %d", [_imageFormat name], index, [self _maximumCount]];
499+
[[FICImageCache sharedImageCache] _logMessage:message];
467500
}
468501

469502
return index;
470503
}
471504

505+
- (NSString *)oldestEvictableEntityUUID {
506+
NSString *uuid = nil;
507+
for (NSInteger i = [_MRUEntries count] - 1; i >= 0; i--) {
508+
NSString *candidateUUID = [_MRUEntries objectAtIndex:i];
509+
if (![_inUseEntries containsObject:candidateUUID]) {
510+
uuid = candidateUUID;
511+
break;
512+
}
513+
}
514+
515+
return uuid;
516+
}
517+
472518
- (NSInteger)_indexOfEntryForEntityUUID:(NSString *)entityUUID {
473519
NSInteger index = NSNotFound;
474520
if (_indexMap != nil && entityUUID != nil) {
@@ -570,6 +616,7 @@ - (void)reset {
570616

571617
[_indexMap removeAllObjects];
572618
[_occupiedIndexes removeAllIndexes];
619+
[_inUseEntries removeAllObjects];
573620
[_MRUEntries removeAllObjects];
574621
[_sourceImageMap removeAllObjects];
575622

FastImageCache/FICImageTableEntry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ typedef struct {
6969
*/
7070
- (instancetype)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void *)bytes length:(size_t)length;
7171

72+
- (void)executeBlockOnDealloc:(dispatch_block_t)block;
73+
7274
///--------------------------------------------
7375
/// @name Flushing a Modified Image Table Entry
7476
///--------------------------------------------

FastImageCache/FICImageTableEntry.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ @interface FICImageTableEntry () {
2121
FICImageTableChunk *_imageTableChunk;
2222
void *_bytes;
2323
size_t _length;
24+
dispatch_block_t _deallocBlock;
2425
}
2526

2627
@end
@@ -68,6 +69,16 @@ - (id)initWithImageTableChunk:(FICImageTableChunk *)imageTableChunk bytes:(void
6869
return self;
6970
}
7071

72+
- (void)executeBlockOnDealloc:(dispatch_block_t)block {
73+
_deallocBlock = [block copy];
74+
}
75+
76+
- (void)dealloc {
77+
if (_deallocBlock) {
78+
_deallocBlock();
79+
}
80+
}
81+
7182
#pragma mark - Other Accessors
7283

7384
+ (NSInteger)metadataVersion {

0 commit comments

Comments
 (0)