Răsfoiți Sursa

Fix the deadlock by using the two different semaphore, the `@synchronized` can not represent two pointer array lock and may be entered twice

DreamPiggy 6 ani în urmă
părinte
comite
fe730cc7e4
1 a modificat fișierele cu 38 adăugiri și 23 ștergeri
  1. 38 23
      SDWebImage/Core/SDWebImagePrefetcher.m

+ 38 - 23
SDWebImage/Core/SDWebImagePrefetcher.m

@@ -20,6 +20,10 @@
     atomic_flag  _isAllFinished;
     
     unsigned long _totalCount;
+    
+    // Used to ensure NSPointerArray thread safe
+    dispatch_semaphore_t _prefetchOperationsLock;
+    dispatch_semaphore_t _loadOperationsLock;
 }
 
 @property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
@@ -106,7 +110,6 @@
 }
 
 - (void)startPrefetchWithToken:(SDWebImagePrefetchToken * _Nonnull)token {
-    NSPointerArray *operations = token.loadOperations;
     for (NSURL *url in token.urls) {
         @autoreleasepool {
             @weakify(self);
@@ -142,13 +145,13 @@
                     [asyncOperation complete];
                 }];
                 NSAssert(operation != nil, @"Operation should not be nil, [SDWebImageManager loadImageWithURL:options:context:progress:completed:] break prefetch logic");
-                @synchronized (token) {
-                    [operations addPointer:(__bridge void *)operation];
-                }
+                SD_LOCK(token->_loadOperationsLock);
+                [token.loadOperations addPointer:(__bridge void *)operation];
+                SD_UNLOCK(token->_loadOperationsLock);
             }];
-            @synchronized (token) {
-                [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation];
-            }
+            SD_LOCK(token->_prefetchOperationsLock);
+            [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation];
+            SD_UNLOCK(token->_prefetchOperationsLock);
             [self.prefetchQueue addOperation:prefetchOperation];
         }
     }
@@ -262,26 +265,38 @@
 
 @implementation SDWebImagePrefetchToken
 
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _prefetchOperationsLock = dispatch_semaphore_create(1);
+        _loadOperationsLock = dispatch_semaphore_create(1);
+    }
+    return self;
+}
+
 - (void)cancel {
-    @synchronized (self) {
-        [self.prefetchOperations compact];
-        for (id operation in self.prefetchOperations) {
-            id<SDWebImageOperation> strongOperation = operation;
-            if (strongOperation) {
-                [strongOperation cancel];
-            }
+    SD_LOCK(_prefetchOperationsLock);
+    [self.prefetchOperations compact];
+    for (id operation in self.prefetchOperations) {
+        id<SDWebImageOperation> strongOperation = operation;
+        if (strongOperation) {
+            [strongOperation cancel];
         }
-        self.prefetchOperations.count = 0;
-        
-        [self.loadOperations compact];
-        for (id operation in self.loadOperations) {
-            id<SDWebImageOperation> strongOperation = operation;
-            if (strongOperation) {
-                [strongOperation cancel];
-            }
+    }
+    self.prefetchOperations.count = 0;
+    SD_UNLOCK(_prefetchOperationsLock);
+    
+    SD_LOCK(_loadOperationsLock);
+    [self.loadOperations compact];
+    for (id operation in self.loadOperations) {
+        id<SDWebImageOperation> strongOperation = operation;
+        if (strongOperation) {
+            [strongOperation cancel];
         }
-        self.loadOperations.count = 0;
     }
+    self.loadOperations.count = 0;
+    SD_UNLOCK(_loadOperationsLock);
+    
     self.completionBlock = nil;
     self.progressBlock = nil;
     [self.prefetcher removeRunningToken:self];