Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 202c947

Browse files
garrettmoonAdlai Holler
authored andcommitted
[ASNetworkImageNode] Don't lock while calling downloader (#2864)
* Don't lock while calling downloader Addresses #2785 To avoid performance issues, we should avoid locking the downloader. To achieve this we need to do some kinda gross things. Essentially the cost is the code is more complex and potentially far less performant in edge cases. In testing, edge cases are nearly never hit, but I'm not sure how good I feel about the cost in code complexity. This exacerbates the locking issues in ASNetworkImageNode: 1. There is no convention for which methods lock. 2. There's no indication which vars are only set on init and therefore safe to access except in the class extension definition. * Shouldn't have checked in product changes. * Using ivar instead of local var copied within lock.
1 parent 2bc701d commit 202c947

1 file changed

Lines changed: 90 additions & 29 deletions

File tree

AsyncDisplayKit/ASNetworkImageNode.mm

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525

2626
@interface ASNetworkImageNode ()
2727
{
28-
__weak id<ASImageCacheProtocol> _cache;
29-
__weak id<ASImageDownloaderProtocol> _downloader;
30-
3128
// Only access any of these with __instanceLock__.
3229
__weak id<ASNetworkImageNodeDelegate> _delegate;
3330

@@ -53,6 +50,9 @@ @interface ASNetworkImageNode ()
5350
} _delegateFlags;
5451

5552
//set on init only
53+
__weak id<ASImageDownloaderProtocol> _downloader;
54+
__weak id<ASImageCacheProtocol> _cache;
55+
5656
struct {
5757
unsigned int downloaderImplementsSetProgress:1;
5858
unsigned int downloaderImplementsSetPriority:1;
@@ -236,11 +236,13 @@ - (void)setDelegate:(id<ASNetworkImageNodeDelegate>)delegate
236236

237237
- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages
238238
{
239-
ASDN::MutexLocker l(__instanceLock__);
240-
if (shouldRenderProgressImages == _shouldRenderProgressImages) {
241-
return;
239+
{
240+
ASDN::MutexLocker l(__instanceLock__);
241+
if (shouldRenderProgressImages == _shouldRenderProgressImages) {
242+
return;
243+
}
244+
_shouldRenderProgressImages = shouldRenderProgressImages;
242245
}
243-
_shouldRenderProgressImages = shouldRenderProgressImages;
244246

245247
[self _updateProgressImageBlockOnDownloaderIfNeeded];
246248
}
@@ -293,28 +295,41 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously
293295
- (void)didEnterVisibleState
294296
{
295297
[super didEnterVisibleState];
296-
ASDN::MutexLocker l(__instanceLock__);
298+
299+
id downloadIdentifier = nil;
300+
301+
{
302+
ASDN::MutexLocker l(__instanceLock__);
297303

298-
if (_downloaderFlags.downloaderImplementsSetPriority) {
299-
if (_downloadIdentifier != nil) {
300-
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier];
304+
if (_downloaderFlags.downloaderImplementsSetPriority) {
305+
downloadIdentifier = _downloadIdentifier;
301306
}
302307
}
303308

309+
if (downloadIdentifier != nil) {
310+
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:downloadIdentifier];
311+
}
312+
304313
[self _updateProgressImageBlockOnDownloaderIfNeeded];
305314
}
306315

307316
- (void)didExitVisibleState
308317
{
309318
[super didExitVisibleState];
310-
ASDN::MutexLocker l(__instanceLock__);
319+
320+
id downloadIdentifier = nil;
311321

312-
if (_downloaderFlags.downloaderImplementsSetPriority) {
313-
if (_downloadIdentifier != nil) {
314-
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier];
322+
{
323+
ASDN::MutexLocker l(__instanceLock__);
324+
if (_downloaderFlags.downloaderImplementsSetPriority) {
325+
downloadIdentifier = _downloadIdentifier;
315326
}
316327
}
317328

329+
if (downloadIdentifier != nil) {
330+
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:downloadIdentifier];
331+
}
332+
318333
[self _updateProgressImageBlockOnDownloaderIfNeeded];
319334
}
320335

@@ -370,9 +385,16 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded
370385
}
371386

372387
// Read state.
373-
BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState);
374-
id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock;
375-
id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil;
388+
BOOL shouldRender;
389+
id oldDownloadIDForProgressBlock;
390+
id newDownloadIDForProgressBlock;
391+
BOOL clearAndReattempt = NO;
392+
{
393+
ASDN::MutexLocker l(__instanceLock__);
394+
shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState);
395+
oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock;
396+
newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil;
397+
}
376398

377399
// If we're already bound to the correct download, we're done.
378400
if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) {
@@ -393,7 +415,19 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded
393415
}
394416

395417
// Update state.
396-
_downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock;
418+
{
419+
ASDN::MutexLocker l(__instanceLock__);
420+
if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) {
421+
_downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock;
422+
} else {
423+
clearAndReattempt = YES;
424+
}
425+
}
426+
427+
if (clearAndReattempt) {
428+
[_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock];
429+
[self _updateProgressImageBlockOnDownloaderIfNeeded];
430+
}
397431
}
398432

399433
- (void)_cancelDownloadAndClearImage
@@ -443,19 +477,46 @@ - (void)_clearImage
443477
- (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier))finished
444478
{
445479
ASPerformBlockOnBackgroundThread(^{
480+
NSURL *url;
481+
id downloadIdentifier;
482+
BOOL cancelAndReattempt = NO;
446483

447-
ASDN::MutexLocker l(__instanceLock__);
448-
_downloadIdentifier = [_downloader downloadImageWithURL:_URL
449-
callbackQueue:dispatch_get_main_queue()
450-
downloadProgress:NULL
451-
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
452-
if (finished != NULL) {
453-
finished(imageContainer, error, downloadIdentifier);
454-
}
455-
}];
484+
// Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because
485+
// We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel
486+
// it and try again.
487+
{
488+
ASDN::MutexLocker l(__instanceLock__);
489+
url = _URL;
490+
}
491+
492+
downloadIdentifier = [_downloader downloadImageWithURL:url
493+
callbackQueue:dispatch_get_main_queue()
494+
downloadProgress:NULL
495+
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
496+
if (finished != NULL) {
497+
finished(imageContainer, error, downloadIdentifier);
498+
}
499+
}];
456500

501+
{
502+
ASDN::MutexLocker l(__instanceLock__);
503+
if ([_URL isEqual:url]) {
504+
// The download we kicked off is correct, no need to do any more work.
505+
_downloadIdentifier = downloadIdentifier;
506+
} else {
507+
// The URL changed since we kicked off our download task. This shouldn't happen often so we'll pay the cost and
508+
// cancel that request and kick off a new one.
509+
cancelAndReattempt = YES;
510+
}
511+
}
512+
513+
if (cancelAndReattempt) {
514+
[_downloader cancelImageDownloadForIdentifier:downloadIdentifier];
515+
[self _downloadImageWithCompletion:finished];
516+
return;
517+
}
518+
457519
[self _updateProgressImageBlockOnDownloaderIfNeeded];
458-
459520
});
460521
}
461522

0 commit comments

Comments
 (0)