Skip to content

Potential cache race can result in memory/disk cache inconsistency #48

@jaredru

Description

@jaredru

When [TMCache objectForKey:block:] fails to find an object in memory, it falls back to the disk. If it finds the object on disk, it sets the object in memory. This makes sense.

However, if this is happening at the same time a new object is being set for the same key, there's a race to set the object in memory because [TMCache setObject:forKey:block:] sets the memory and disk caches in parallel.

Essentially, if calls to [TMCache objectForKey:block:] and [TMCache setObject:forKey:block:] are made in parallel, there's a chance the underlying calls land in this order:

  1. The get on the memory cache misses the object.
  2. The set on the memory cache sets the new object.
  3. The get on the disk cache finds the old object and it is re-set on the memory cache.
  4. The set on the disk cache sets the new object.

Until the memory cache is blown away, it will subsequently return the wrong value. This sample code reproduces the issue (it's a race, so obviously it won't repro every time):

TMCache* cache = [TMCache sharedCache];
[cache removeAllObjects];
[cache setObject:@"old" forKey:@"key"];
[cache.memoryCache removeAllObjects];

[cache objectForKey:@"key" block:^(TMCache* cache, NSString* key, id object) {
    NSLog(@"get %@", object);
}];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    [cache setObject:@"new" forKey:@"key" block:^(TMCache* cache, NSString* key, id object) {
        NSLog(@"set %@", object);
        NSLog(@"get2 %@", [cache objectForKey:@"key"]);
    }];
});

I expect that this issue manifests extremely rarely, but I figured it's worth pointing out anyway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions