@@ -46,20 +46,22 @@ @interface PINDiskCacheMetadata : NSObject
46
46
@end
47
47
48
48
@interface PINDiskCache () {
49
- NSConditionLock *_instanceLock;
50
-
51
49
PINDiskCacheSerializerBlock _serializer;
52
50
PINDiskCacheDeserializerBlock _deserializer;
53
51
54
52
PINDiskCacheKeyEncoderBlock _keyEncoder;
55
53
PINDiskCacheKeyDecoderBlock _keyDecoder;
56
54
}
57
55
56
+ @property (assign , nonatomic ) pthread_mutex_t mutex;
58
57
@property (copy , nonatomic ) NSString *name;
59
58
@property (assign ) NSUInteger byteCount;
60
59
@property (strong , nonatomic ) NSURL *cacheURL;
61
60
@property (strong , nonatomic ) PINOperationQueue *operationQueue;
62
61
@property (strong , nonatomic ) NSMutableDictionary <NSString *, PINDiskCacheMetadata *> *metadata;
62
+ @property (assign , nonatomic ) pthread_cond_t diskWritableCondition;
63
+ @property (assign , nonatomic ) BOOL diskWritable;
64
+ @property (assign , nonatomic ) pthread_cond_t diskStateKnownCondition;
63
65
@property (assign , nonatomic ) BOOL diskStateKnown;
64
66
@end
65
67
@@ -83,6 +85,14 @@ @implementation PINDiskCache
83
85
84
86
#pragma mark - Initialization -
85
87
88
+ - (void )dealloc
89
+ {
90
+ __unused int result = pthread_mutex_destroy (&_mutex);
91
+ NSCAssert (result == 0 , @" Failed to destroy lock in PINMemoryCache %p . Code: %d " , (void *)self, result);
92
+ pthread_cond_destroy (&_diskWritableCondition);
93
+ pthread_cond_destroy (&_diskStateKnownCondition);
94
+ }
95
+
86
96
- (instancetype )init
87
97
{
88
98
@throw [NSException exceptionWithName: @" Must initialize with a name" reason: @" PINDiskCache must be initialized with a name. Call initWithName: instead." userInfo: nil ];
@@ -143,10 +153,12 @@ - (instancetype)initWithName:(NSString *)name
143
153
@"PINDiskCache must be initialized with a encoder AND decoder.");
144
154
145
155
if (self = [super init ]) {
156
+ __unused int result = pthread_mutex_init (&_mutex, NULL );
157
+ NSAssert (result == 0 , @" Failed to init lock in PINMemoryCache %@ . Code: %d " , self, result);
158
+
146
159
_name = [name copy ];
147
160
_prefix = [prefix copy ];
148
161
_operationQueue = operationQueue;
149
- _instanceLock = [[NSConditionLock alloc ] initWithCondition: PINDiskCacheConditionNotReady];
150
162
_willAddObjectBlock = nil ;
151
163
_willRemoveObjectBlock = nil ;
152
164
_willRemoveAllObjectsBlock = nil ;
@@ -195,14 +207,16 @@ - (instancetype)initWithName:(NSString *)name
195
207
} else {
196
208
_keyDecoder = self.defaultKeyDecoder ;
197
209
}
210
+
211
+ pthread_cond_init (&_diskWritableCondition, NULL );
212
+ pthread_cond_init (&_diskStateKnownCondition, NULL );
198
213
199
214
// we don't want to do anything without setting up the disk cache, but we also don't want to block init, it can take a while to initialize. This must *not* be done on _operationQueue because other operations added may hold the lock and fill up the queue.
200
215
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
201
- // should always be able to aquire the lock unless the below code is running.
202
- [_instanceLock lockWhenCondition: PINDiskCacheConditionNotReady];
216
+ [self lock ];
203
217
[self _locked_createCacheDirectory ];
204
- [self _locked_initializeDiskProperties ];
205
- [_instanceLock unlockWithCondition: PINDiskCacheConditionReady ];
218
+ [self unlock ];
219
+ [self initializeDiskProperties ];
206
220
});
207
221
}
208
222
return self;
@@ -415,55 +429,77 @@ + (void)emptyTrash
415
429
416
430
- (BOOL )_locked_createCacheDirectory
417
431
{
418
- if ([[NSFileManager defaultManager ] fileExistsAtPath: [_cacheURL path ]])
419
- return NO ;
432
+ BOOL created = NO ;
433
+ if ([[NSFileManager defaultManager ] fileExistsAtPath: [_cacheURL path ]] == NO ) {
434
+ NSError *error = nil ;
435
+ created = [[NSFileManager defaultManager ] createDirectoryAtURL: _cacheURL
436
+ withIntermediateDirectories: YES
437
+ attributes: nil
438
+ error: &error];
439
+ PINDiskCacheError (error);
440
+ }
420
441
421
- NSError *error = nil ;
422
- BOOL success = [[NSFileManager defaultManager ] createDirectoryAtURL: _cacheURL
423
- withIntermediateDirectories: YES
424
- attributes: nil
425
- error: &error];
426
- PINDiskCacheError (error);
442
+
427
443
428
- return success;
444
+ // while this may not be true if success is false, it's better than deadlocking later.
445
+ _diskWritable = YES ;
446
+ pthread_cond_broadcast (&_diskWritableCondition);
447
+
448
+ return created;
429
449
}
430
450
431
- - (void )_locked_initializeDiskProperties
451
+ - (void )initializeDiskProperties
432
452
{
433
453
NSUInteger byteCount = 0 ;
434
454
NSArray *keys = @[ NSURLContentModificationDateKey , NSURLTotalFileAllocatedSizeKey ];
435
455
436
456
NSError *error = nil ;
437
- NSArray *files = [[NSFileManager defaultManager ] contentsOfDirectoryAtURL: _cacheURL
438
- includingPropertiesForKeys: keys
439
- options: NSDirectoryEnumerationSkipsHiddenFiles
440
- error: &error];
457
+
458
+ [self lock ];
459
+ NSArray *files = [[NSFileManager defaultManager ] contentsOfDirectoryAtURL: _cacheURL
460
+ includingPropertiesForKeys: keys
461
+ options: NSDirectoryEnumerationSkipsHiddenFiles
462
+ error: &error];
463
+ [self unlock ];
464
+
441
465
PINDiskCacheError (error);
442
466
443
467
for (NSURL *fileURL in files) {
444
468
NSString *key = [self keyForEncodedFileURL: fileURL];
445
469
446
470
error = nil ;
447
- NSDictionary *dictionary = [fileURL resourceValuesForKeys: keys error: &error];
448
- PINDiskCacheError (error);
449
471
450
- _metadata[key] = [[PINDiskCacheMetadata alloc ] init ];
472
+ // Continually grab and release lock while processing files to avoid contention
473
+ [self lock ];
474
+ NSDictionary *dictionary = [fileURL resourceValuesForKeys: keys error: &error];
475
+ PINDiskCacheError (error);
476
+
477
+ if (_metadata[key] == nil ) {
478
+ _metadata[key] = [[PINDiskCacheMetadata alloc ] init ];
479
+ }
451
480
452
- NSDate *date = [dictionary objectForKey: NSURLContentModificationDateKey ];
453
- if (date && key)
454
- _metadata[key].date = date;
481
+ NSDate *date = [dictionary objectForKey: NSURLContentModificationDateKey ];
482
+ if (date && key)
483
+ _metadata[key].date = date;
455
484
456
- NSNumber *fileSize = [dictionary objectForKey: NSURLTotalFileAllocatedSizeKey ];
457
- if (fileSize) {
458
- _metadata[key].size = fileSize;
459
- byteCount += [fileSize unsignedIntegerValue ];
460
- }
485
+ NSNumber *fileSize = [dictionary objectForKey: NSURLTotalFileAllocatedSizeKey ];
486
+ if (fileSize) {
487
+ _metadata[key].size = fileSize;
488
+ byteCount += [fileSize unsignedIntegerValue ];
489
+ }
490
+ [self unlock ];
461
491
}
462
492
463
- if (byteCount > 0 )
464
- _byteCount = byteCount;
493
+ [self lock ];
494
+ if (byteCount > 0 )
495
+ _byteCount = byteCount;
496
+
497
+ if (self->_byteLimit > 0 && self->_byteCount > self->_byteLimit )
498
+ [self trimToSizeByDateAsync: self ->_byteLimit completion: nil ];
465
499
466
- _diskStateKnown = YES ;
500
+ _diskStateKnown = YES ;
501
+ pthread_cond_broadcast (&_diskStateKnownCondition);
502
+ [self unlock ];
467
503
}
468
504
469
505
- (void )asynchronouslySetFileModificationDate : (NSDate *)date forURL : (NSURL *)fileURL
@@ -472,7 +508,7 @@ - (void)asynchronouslySetFileModificationDate:(NSDate *)date forURL:(NSURL *)fil
472
508
[self .operationQueue addOperation: ^{
473
509
PINDiskCache *strongSelf = weakSelf;
474
510
if (strongSelf) {
475
- [strongSelf lock ];
511
+ [strongSelf lockForWriting ];
476
512
[strongSelf _locked_setFileModificationDate: date forURL: fileURL];
477
513
[strongSelf unlock ];
478
514
}
@@ -505,7 +541,8 @@ - (BOOL)removeFileAndExecuteBlocksForKey:(NSString *)key
505
541
{
506
542
NSURL *fileURL = [self encodedFileURLForKey: key];
507
543
508
- [self lock ];
544
+ // We only need to lock until writable at the top because once writable, always writable
545
+ [self lockForWriting ];
509
546
if (!fileURL || ![[NSFileManager defaultManager ] fileExistsAtPath: [fileURL path ]]) {
510
547
[self unlock ];
511
548
return NO ;
@@ -546,7 +583,7 @@ - (BOOL)removeFileAndExecuteBlocksForKey:(NSString *)key
546
583
547
584
- (void )trimDiskToSize : (NSUInteger )trimByteCount
548
585
{
549
- [self lock ];
586
+ [self lockForWriting ];
550
587
if (_byteCount > trimByteCount) {
551
588
NSArray *keysSortedBySize = [_metadata keysSortedByValueUsingComparator: ^NSComparisonResult (PINDiskCacheMetadata * _Nonnull obj1, PINDiskCacheMetadata * _Nonnull obj2) {
552
589
return [obj1.size compare: obj2.size];
@@ -569,7 +606,7 @@ - (void)trimDiskToSize:(NSUInteger)trimByteCount
569
606
570
607
- (void )trimDiskToSizeByDate : (NSUInteger )trimByteCount
571
608
{
572
- [self lock ];
609
+ [self lockForWriting ];
573
610
if (_byteCount > trimByteCount) {
574
611
NSArray *keysSortedByDate = [_metadata keysSortedByValueUsingComparator: ^NSComparisonResult (PINDiskCacheMetadata * _Nonnull obj1, PINDiskCacheMetadata * _Nonnull obj2) {
575
612
return [obj1.date compare: obj2.date];
@@ -592,7 +629,7 @@ - (void)trimDiskToSizeByDate:(NSUInteger)trimByteCount
592
629
593
630
- (void )trimDiskToDate : (NSDate *)trimDate
594
631
{
595
- [self lock ];
632
+ [self lockForWriting ];
596
633
NSArray *keysSortedByDate = [_metadata keysSortedByValueUsingComparator: ^NSComparisonResult (PINDiskCacheMetadata * _Nonnull obj1, PINDiskCacheMetadata * _Nonnull obj2) {
597
634
return [obj1.date compare: obj2.date];
598
635
}];
@@ -650,7 +687,7 @@ - (void)lockFileAccessWhileExecutingBlockAsync:(PINCacheBlock)block
650
687
651
688
[self .operationQueue addOperation: ^{
652
689
PINDiskCache *strongSelf = weakSelf;
653
- [strongSelf lock ];
690
+ [strongSelf lockForWriting ];
654
691
block (strongSelf);
655
692
[strongSelf unlock ];
656
693
} withPriority: PINOperationQueuePriorityLow];
@@ -693,7 +730,7 @@ - (void)fileURLForKeyAsync:(NSString *)key completion:(PINDiskCacheFileURLBlock)
693
730
PINDiskCache *strongSelf = weakSelf;
694
731
NSURL *fileURL = [strongSelf fileURLForKey: key];
695
732
696
- [strongSelf lock ];
733
+ [strongSelf lockForWriting ];
697
734
block (key, fileURL);
698
735
[strongSelf unlock ];
699
736
} withPriority: PINOperationQueuePriorityLow];
@@ -830,17 +867,20 @@ - (void)enumerateObjectsWithBlockAsync:(PINDiskCacheFileURLEnumerationBlock)bloc
830
867
- (void )synchronouslyLockFileAccessWhileExecutingBlock : (PINCacheBlock)block
831
868
{
832
869
if (block) {
833
- [self lock ];
870
+ [self lockForWriting ];
834
871
block (self);
835
872
[self unlock ];
836
873
}
837
874
}
838
875
839
876
- (BOOL )containsObjectForKey : (NSString *)key
840
877
{
841
- if (_metadata[key] != nil ) {
842
- return ([self fileURLForKey: key updateFileModificationDate: NO ] != nil );
843
- }
878
+ [self lock ];
879
+ if (_metadata[key] != nil || _diskStateKnown == NO ) {
880
+ [self unlock ];
881
+ return ([self fileURLForKey: key updateFileModificationDate: NO ] != nil );
882
+ }
883
+ [self unlock ];
844
884
return NO ;
845
885
}
846
886
@@ -856,20 +896,24 @@ - (id)objectForKeyedSubscript:(NSString *)key
856
896
857
897
- (nullable id <NSCoding >)objectForKey : (NSString *)key fileURL : (NSURL **)outFileURL
858
898
{
859
- NSDate *now = [[NSDate alloc ] init ];
860
-
861
899
[self lock ];
862
- BOOL isEmpty = _metadata.count == 0 ;
863
- BOOL containsKey = _metadata[key] != nil ;
900
+ BOOL containsKey = _metadata[key] != nil || _diskStateKnown == NO ;
864
901
[self unlock ];
865
902
866
- if (!key || isEmpty || !containsKey)
903
+ if (!key || !containsKey)
867
904
return nil ;
868
905
869
906
id <NSCoding > object = nil ;
870
907
NSURL *fileURL = [self encodedFileURLForKey: key];
871
908
909
+ NSDate *now = [[NSDate alloc ] init ];
872
910
[self lock ];
911
+ if (self->_ttlCache ) {
912
+ // We actually need to know the entire disk state if we're a TTL cache.
913
+ [self unlock ];
914
+ [self lockAndWaitForKnownState ];
915
+ }
916
+
873
917
if (!self->_ttlCache || self->_ageLimit <= 0 || fabs ([_metadata[key].date timeIntervalSinceDate: now]) < self->_ageLimit ) {
874
918
// If the cache should behave like a TTL cache, then only fetch the object if there's a valid ageLimit and the object is still alive
875
919
@@ -920,7 +964,7 @@ - (NSURL *)fileURLForKey:(NSString *)key updateFileModificationDate:(BOOL)update
920
964
NSDate *now = [[NSDate alloc ] init ];
921
965
NSURL *fileURL = [self encodedFileURLForKey: key];
922
966
923
- [self lock ];
967
+ [self lockForWriting ];
924
968
if (fileURL.path && [[NSFileManager defaultManager ] fileExistsAtPath: fileURL.path]) {
925
969
if (updateFileModificationDate) {
926
970
[self asynchronouslySetFileModificationDate: now forURL: fileURL];
@@ -979,7 +1023,7 @@ - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key fileURL:(NSURL **
979
1023
return ;
980
1024
}
981
1025
982
- [self lock ];
1026
+ [self lockForWriting ];
983
1027
PINCacheObjectBlock willAddObjectBlock = self->_willAddObjectBlock ;
984
1028
if (willAddObjectBlock) {
985
1029
[self unlock ];
@@ -1089,11 +1133,12 @@ - (void)trimToSizeByDate:(NSUInteger)trimByteCount
1089
1133
1090
1134
- (void )removeAllObjects
1091
1135
{
1092
- [self lock ];
1136
+ // We don't need to know the disk state since we're just going to remove everything.
1137
+ [self lockForWriting ];
1093
1138
PINCacheBlock willRemoveAllObjectsBlock = self->_willRemoveAllObjectsBlock ;
1094
1139
if (willRemoveAllObjectsBlock) {
1095
1140
[self unlock ];
1096
- willRemoveAllObjectsBlock (self);
1141
+ willRemoveAllObjectsBlock (self);
1097
1142
[self lock ];
1098
1143
}
1099
1144
@@ -1120,7 +1165,7 @@ - (void)enumerateObjectsWithBlock:(PINDiskCacheFileURLEnumerationBlock)block
1120
1165
if (!block)
1121
1166
return ;
1122
1167
1123
- [self lock ];
1168
+ [self lockAndWaitForKnownState ];
1124
1169
NSDate *now = [NSDate date ];
1125
1170
1126
1171
for (NSString *key in _metadata) {
@@ -1397,20 +1442,42 @@ - (void)setWritingProtectionOption:(NSDataWritingOptions)writingProtectionOption
1397
1442
NSDataWritingOptions option = NSDataWritingFileProtectionMask & writingProtectionOption;
1398
1443
1399
1444
[strongSelf lock ];
1400
- strongSelf->_writingProtectionOption = option;
1445
+ strongSelf->_writingProtectionOption = option;
1401
1446
[strongSelf unlock ];
1402
1447
} withPriority: PINOperationQueuePriorityHigh];
1403
1448
}
1404
1449
#endif
1405
1450
1451
+ - (void )lockForWriting
1452
+ {
1453
+ [self lock ];
1454
+
1455
+ // spinlock if the disk isn't writable
1456
+ if (_diskWritable == NO ) {
1457
+ pthread_cond_wait (&_diskWritableCondition, &_mutex);
1458
+ }
1459
+ }
1460
+
1461
+ - (void )lockAndWaitForKnownState
1462
+ {
1463
+ [self lock ];
1464
+
1465
+ // spinlock if the disk state isn't known
1466
+ if (_diskStateKnown == NO ) {
1467
+ pthread_cond_wait (&_diskStateKnownCondition, &_mutex);
1468
+ }
1469
+ }
1470
+
1406
1471
- (void )lock
1407
1472
{
1408
- [_instanceLock lockWhenCondition: PINDiskCacheConditionReady];
1473
+ __unused int result = pthread_mutex_lock (&_mutex);
1474
+ NSAssert (result == 0 , @" Failed to lock PINDiskCache %@ . Code: %d " , self, result);
1409
1475
}
1410
1476
1411
1477
- (void )unlock
1412
1478
{
1413
- [_instanceLock unlockWithCondition: PINDiskCacheConditionReady];
1479
+ __unused int result = pthread_mutex_unlock (&_mutex);
1480
+ NSAssert (result == 0 , @" Failed to unlock PINDiskCache %@ . Code: %d " , self, result);
1414
1481
}
1415
1482
1416
1483
@end
0 commit comments