Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweaks to garbage collection #90

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
17 changes: 16 additions & 1 deletion SPTPersistentCache.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = SPTPersistentCache;
LastUpgradeCheck = 0820;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Spotify;
TargetAttributes = {
0510FF121BA2FF7A00ED0766 = {
Expand Down Expand Up @@ -492,21 +492,35 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9C882A801C65422700D463BB /* project.xcconfig */;
buildSettings = {
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to put all of these in our xcconfig file. To keep the Xcode project clean and make it easier to maintain. See https://github.com/spotify/SPTPersistentCache/blob/master/ci/spotify_os.xcconfig

CLANG_WARN_COMMA = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_DYNAMIC_NO_PIC = NO;
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
HEADER_SEARCH_PATHS = (
include,
"$(inherited)",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
};
name = Debug;
};
0510FF261BA2FF7A00ED0766 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C882A801C65422700D463BB /* project.xcconfig */;
buildSettings = {
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
HEADER_SEARCH_PATHS = (
include,
Expand All @@ -521,6 +535,7 @@
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0820"
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
8 changes: 7 additions & 1 deletion Sources/SPTPersistentCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ - (void)unscheduleGarbageCollector
[self.garbageCollector unschedule];
}

- (void)enqueueGarbageCollector
{
[self.garbageCollector enqueueGarbageCollection];
}

- (void)pruneWithCallback:(SPTPersistentCacheResponseCallback _Nullable)callback
onQueue:(dispatch_queue_t _Nullable)queue
{
Expand Down Expand Up @@ -1043,7 +1048,8 @@ - (NSMutableArray *)storedImageNamesAndAttributes
NSNumber *fsize = [NSNumber numberWithLongLong:fileStat.st_size];
NSDictionary *values = @{NSFileModificationDate : mdate, NSFileSize: fsize};

[images addObject:@{ SPTDataCacheFileNameKey : [NSString stringWithUTF8String:[theURL fileSystemRepresentation]],
NSString *cacheFilename = [NSString stringWithUTF8String:[theURL fileSystemRepresentation]];
[images addObject:@{ SPTDataCacheFileNameKey : cacheFilename,
SPTDataCacheFileAttributesKey : values }];
}
} else {
Expand Down
4 changes: 1 addition & 3 deletions Sources/SPTPersistentCacheFileManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
#import "SPTPersistentCacheDebugUtilities.h"
#import "SPTPersistentCacheOptions.h"

static const double SPTPersistentCacheFileManagerMinFreeDiskSpace = 0.1;

const NSUInteger SPTPersistentCacheFileManagerSubDirNameLength = 2;

@implementation SPTPersistentCacheFileManager
Expand Down Expand Up @@ -179,7 +177,7 @@ - (SPTPersistentCacheDiskSize)optimizedDiskSizeForCacheSize:(SPTPersistentCacheD
SPTPersistentCacheDiskSize totalSpace = fileSystemSize.longLongValue;
SPTPersistentCacheDiskSize freeSpace = fileSystemFreeSpace.longLongValue + currentCacheSize;
SPTPersistentCacheDiskSize proposedCacheSize = freeSpace - llrint(totalSpace *
SPTPersistentCacheFileManagerMinFreeDiskSpace);
self.options.minimumFreeDiskSpaceFraction);

tempCacheSize = MAX(0, proposedCacheSize);

Expand Down
5 changes: 5 additions & 0 deletions Sources/SPTPersistentCacheGarbageCollector.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,9 @@
*/
- (void)unschedule;

/**
* Enqueues the garbage collector. Called automatically if scheduled by the above functions.
*/
- (void)enqueueGarbageCollection;

@end
9 changes: 7 additions & 2 deletions Sources/SPTPersistentCacheGarbageCollector.m
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ - (void)dealloc

#pragma mark -

- (void)enqueueGarbageCollection:(NSTimer *)timer
- (void)enqueueGarbageCollection
{
__weak __typeof(self) const weakSelf = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
Expand All @@ -81,6 +81,11 @@ - (void)enqueueGarbageCollection:(NSTimer *)timer
[self.queue addOperation:operation];
}

- (void)garbageCollectionTimerFired:(NSTimer *)timer
{
[self enqueueGarbageCollection];
}

- (void)schedule
{
if (!SPTPersistentCacheGarbageCollectorSchedulerIsInMainQueue()) {
Expand All @@ -100,7 +105,7 @@ - (void)schedule

self.timer = [NSTimer timerWithTimeInterval:self.options.garbageCollectionInterval
target:self
selector:@selector(enqueueGarbageCollection:)
selector:@selector(garbageCollectionTimerFired:)
userInfo:nil
repeats:YES];

Expand Down
3 changes: 3 additions & 0 deletions Sources/SPTPersistentCacheOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
const NSUInteger SPTPersistentCacheDefaultExpirationTimeSec = 10 * 60;
const NSUInteger SPTPersistentCacheDefaultGCIntervalSec = 6 * 60 + 3;
const NSUInteger SPTPersistentCacheDefaultCacheSizeInBytes = 0; // unbounded
const double SPTPersistentCacheDefaultMinFreeDiskSpaceFraction = 0.1; // 10% of total disk size

const NSUInteger SPTPersistentCacheMinimumGCIntervalLimit = 60;
const NSUInteger SPTPersistentCacheMinimumExpirationLimit = 60;
Expand Down Expand Up @@ -55,6 +56,7 @@ - (instancetype)init
_garbageCollectionInterval = SPTPersistentCacheDefaultGCIntervalSec;
_defaultExpirationPeriod = SPTPersistentCacheDefaultExpirationTimeSec;
_sizeConstraintBytes = SPTPersistentCacheDefaultCacheSizeInBytes;
_minimumFreeDiskSpaceFraction = SPTPersistentCacheDefaultMinFreeDiskSpaceFraction;
_maxConcurrentOperations = NSOperationQueueDefaultMaxConcurrentOperationCount;
_writePriority = NSOperationQueuePriorityNormal;
_writeQualityOfService = NSQualityOfServiceDefault;
Expand Down Expand Up @@ -117,6 +119,7 @@ - (id)copyWithZone:(NSZone *)zone
copy.garbageCollectionInterval = self.garbageCollectionInterval;
copy.defaultExpirationPeriod = self.defaultExpirationPeriod;
copy.sizeConstraintBytes = self.sizeConstraintBytes;
copy.minimumFreeDiskSpaceFraction = self.minimumFreeDiskSpaceFraction;

copy.debugOutput = self.debugOutput;
copy.timingCallback = self.timingCallback;
Expand Down
28 changes: 28 additions & 0 deletions Tests/SPTPersistentCacheFileManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ - (void)setUp
SPTPersistentCacheOptions *options = [SPTPersistentCacheOptions new];
options.cachePath = SPTPersistentCacheFileManagerTestsCachePath;
options.cacheIdentifier = @"test";
options.sizeConstraintBytes = (SPTPersistentCacheDiskSize)1024 * 1024 * 1024 * 3; // 3 GiB
self.options = options;

self.cacheFileManager = [[SPTPersistentCacheFileManagerForTests alloc] initWithOptions:self.options];
Expand Down Expand Up @@ -168,6 +169,33 @@ - (void)testOptimizedDiskSizeForCacheSizeSmall
XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)0);
}

- (void)testMinimumFreeDiskSpaceFraction
{
const SPTPersistentCacheDiskSize diskSize = (SPTPersistentCacheDiskSize)1024 * 1024 * 1024 * 16; // 16GiB
const SPTPersistentCacheDiskSize freeSpace = (SPTPersistentCacheDiskSize)1024 * 1024 * 1024 * 8; // 8GiB
NSFileManagerMock *fileManager = [NSFileManagerMock new];
fileManager.mock_attributesOfFileSystemForPaths = @{SPTPersistentCacheFileManagerTestsCachePath: @{NSFileSystemSize: @(diskSize),
NSFileSystemFreeSize: @(freeSpace)}};
self.cacheFileManager.test_fileManager = fileManager;

self.cacheFileManager.options.minimumFreeDiskSpaceFraction = 1.0;
SPTPersistentCacheDiskSize optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:0];
XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)0);

self.cacheFileManager.options.minimumFreeDiskSpaceFraction = 0.0;
optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:0];
XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes);

self.cacheFileManager.options.minimumFreeDiskSpaceFraction = 0.5;
optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:0];
XCTAssertEqual(optimizedSize, (SPTPersistentCacheDiskSize)0);

const SPTPersistentCacheDiskSize twoGiB = (SPTPersistentCacheDiskSize)1024 * 1024 * 1024 * 2;
self.cacheFileManager.options.minimumFreeDiskSpaceFraction = (freeSpace - twoGiB) / (double)diskSize;
optimizedSize = [self.cacheFileManager optimizedDiskSizeForCacheSize:0];
XCTAssertEqual(optimizedSize, twoGiB);
}

- (void)testRemoveAllDataButKeysWithoutKeys
{
NSString *keyOne = @"AA";
Expand Down
21 changes: 19 additions & 2 deletions Tests/SPTPersistentCacheGarbageCollectorTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

@interface SPTPersistentCacheGarbageCollector ()
@property (nonatomic, strong) NSTimer *timer;
- (void)enqueueGarbageCollection:(NSTimer *)timer;
- (void)garbageCollectionTimerFired:(NSTimer *)timer;
@end


Expand Down Expand Up @@ -97,7 +97,7 @@ - (void)testGarbageCollectorEnqueue
dataCacheForUnitTests.queue = self.garbageCollector.queue;

dataCacheForUnitTests.testExpectation = expectation;
[self.garbageCollector enqueueGarbageCollection:nil];
[self.garbageCollector enqueueGarbageCollection];

[self waitForExpectationsWithTimeout:1.0 handler:^(NSError * _Nullable error) {
XCTAssertTrue(dataCacheForUnitTests.wasRunRegularGCCalled);
Expand All @@ -106,6 +106,23 @@ - (void)testGarbageCollectorEnqueue
}];
}

- (void)testGarbageCollectorTimerFired
{
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"testGarbageCollectorEnqueue"];

SPTPersistentCacheForTimerProxyUnitTests *dataCacheForUnitTests = (SPTPersistentCacheForTimerProxyUnitTests *)self.garbageCollector.cache;
dataCacheForUnitTests.queue = self.garbageCollector.queue;

dataCacheForUnitTests.testExpectation = expectation;
[self.garbageCollector garbageCollectionTimerFired:nil];

[self waitForExpectationsWithTimeout:1.0 handler:^(NSError * _Nullable error) {
XCTAssertTrue(dataCacheForUnitTests.wasRunRegularGCCalled);
XCTAssertTrue(dataCacheForUnitTests.wasPruneBySizeCalled);
XCTAssertFalse(dataCacheForUnitTests.wasCalledFromIncorrectQueue);
}];
}

- (void)testIsGarbageCollectionScheduled
{
XCTAssertFalse(self.garbageCollector.isGarbageCollectionScheduled);
Expand Down
12 changes: 12 additions & 0 deletions Tests/SPTPersistentCacheTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,18 @@ - (void)testUnscheduleGarbageCollection
XCTAssertFalse(cache.garbageCollector.isGarbageCollectionScheduled);
}

- (void)testEnqueueGargabeCollection
{
SPTPersistentCache *cache = [self createCacheWithTimeCallback:^NSTimeInterval{
// Exceed expiration interval by 1 sec
return kTestEpochTime + SPTPersistentCacheDefaultExpirationTimeSec + 1;
}
expirationTime:SPTPersistentCacheDefaultExpirationTimeSec];

[cache.garbageCollector enqueueGarbageCollection];
XCTAssertFalse(cache.garbageCollector.isGarbageCollectionScheduled);
}

/**
* This test also checks Req.#1.2 of cache API.
*/
Expand Down
5 changes: 5 additions & 0 deletions include/SPTPersistentCache/SPTPersistentCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ typedef NSString * _Nonnull(^SPTPersistentCacheChooseKeyCallback)(NSArray<NSStri
* Stop garbage collection. If already stopped this method does nothing.
*/
- (void)unscheduleGarbageCollector;
/**
* Run the garbage collector right now. Use this if scheduling isn't appropriate,
* for example in applicationWillResignActive:
*/
- (void)enqueueGarbageCollector;
/**
* Delete all files files in managed folder unconditionaly.
* @param callback May be nil if not interested in result.
Expand Down
7 changes: 7 additions & 0 deletions include/SPTPersistentCache/SPTPersistentCacheOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ FOUNDATION_EXPORT const NSUInteger SPTPersistentCacheMinimumExpirationLimit;
* @note Defaults to `0` (unbounded).
*/
@property (nonatomic, assign) NSUInteger sizeConstraintBytes;
/**
* The minimum fraction of free disk space required for caching. If there is less disk space
* available than this, the cache will be purged during garbage collection until the treshold
* is met. This could mean that the whole cache is evicted if the device is low on space.
* @note Defaults to `0.1`, 10% of the total disk size.
*/
@property (nonatomic, assign) double minimumFreeDiskSpaceFraction;
/**
* The queue priority for garbage collection. Defaults to NSOperationQueuePriorityLow.
*/
Expand Down