Fix missing images
Summary: Under rare and as-yet-to-be determined circumstances, images can sometimes fail to load/download and get "stuck", without producing an error. Because the `RCTNetworkTask` for these images is stuck in the "in progress" state, they clog up the RCTImageLoader task queue, which has a limit of 4 concurrent in-progress tasks. This was previously masked by the fact that we automatically cancelled image requests when the RCTImageView moved offscreen, but we no longer do that. This diff adds logic to detect some types of stuck task and remove them, thereby unblocking the queue. I've also restored the functionality of cancelling downloads for offscreen images (but not unloading the image itself) so that stuck images will be cancelled when you move to another screen, instead of using up space in the queue forever. Reviewed By: fkgozali Differential Revision: D3398105 fbshipit-source-id: 75ee40d06a872ae8e1cb57f02f9cad57c459143c
This commit is contained in:
parent
a05e05fafb
commit
235749ba19
|
@ -67,7 +67,7 @@ RCT_EXPORT_MODULE()
|
||||||
// Set defaults
|
// Set defaults
|
||||||
_maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
|
_maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
|
||||||
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
|
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
|
||||||
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 *1024; // 30MB
|
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB
|
||||||
|
|
||||||
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
|
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
|
||||||
|
@ -241,8 +241,15 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||||
_activeTasks--;
|
_activeTasks--;
|
||||||
break;
|
break;
|
||||||
case RCTNetworkTaskPending:
|
case RCTNetworkTaskPending:
|
||||||
|
break;
|
||||||
case RCTNetworkTaskInProgress:
|
case RCTNetworkTaskInProgress:
|
||||||
// Do nothing
|
// Check task isn't "stuck"
|
||||||
|
if (task.requestToken == nil) {
|
||||||
|
RCTLogWarn(@"Task orphaned for request %@", task.request);
|
||||||
|
[_pendingTasks removeObject:task];
|
||||||
|
_activeTasks--;
|
||||||
|
[task cancel];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,6 +419,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||||
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
completionHandler(error, nil);
|
completionHandler(error, nil);
|
||||||
|
[weakSelf dequeueTasks];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +437,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||||
// Process image data
|
// Process image data
|
||||||
processResponse(response, data, nil);
|
processResponse(response, data, nil);
|
||||||
|
|
||||||
//clean up
|
// Prepare for next task
|
||||||
[weakSelf dequeueTasks];
|
[weakSelf dequeueTasks];
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -442,10 +450,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||||
}
|
}
|
||||||
if (task) {
|
if (task) {
|
||||||
[_pendingTasks addObject:task];
|
[_pendingTasks addObject:task];
|
||||||
if (MAX(_activeTasks, _scheduledDecodes) < _maxConcurrentLoadingTasks) {
|
[weakSelf dequeueTasks];
|
||||||
[task start];
|
|
||||||
_activeTasks++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelLoad = ^{
|
cancelLoad = ^{
|
||||||
|
|
|
@ -315,7 +315,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
{
|
{
|
||||||
[super didMoveToWindow];
|
[super didMoveToWindow];
|
||||||
|
|
||||||
if (self.window && (!self.image || self.image == _defaultImage)) {
|
if (!self.window) {
|
||||||
|
// Cancel loading the image if we've moved offscreen. In addition to helping
|
||||||
|
// pritoritise image requests that are actually on-screen, this removes
|
||||||
|
// requests that have gotten "stuck" from the queue, unblocking other images
|
||||||
|
// from loading.
|
||||||
|
[self cancelImageLoad];
|
||||||
|
} else if (!self.image || self.image == _defaultImage) {
|
||||||
[self reloadImage];
|
[self reloadImage];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ RCT_EXPORT_MODULE()
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSFileManager *fileManager = [NSFileManager new];
|
NSFileManager *fileManager = [NSFileManager new];
|
||||||
NSDictionary<NSString *, id> *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
|
NSDictionary<NSString *, id> *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
|
||||||
if (error) {
|
if (!fileAttributes) {
|
||||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,35 +63,40 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
|
|
||||||
- (void)cancel
|
- (void)cancel
|
||||||
{
|
{
|
||||||
|
_status = RCTNetworkTaskFinished;
|
||||||
__strong id strongToken = _requestToken;
|
__strong id strongToken = _requestToken;
|
||||||
if (strongToken && [_handler respondsToSelector:@selector(cancelRequest:)]) {
|
if (strongToken && [_handler respondsToSelector:@selector(cancelRequest:)]) {
|
||||||
[_handler cancelRequest:strongToken];
|
[_handler cancelRequest:strongToken];
|
||||||
}
|
}
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
_status = RCTNetworkTaskFinished;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)validateRequestToken:(id)requestToken
|
- (BOOL)validateRequestToken:(id)requestToken
|
||||||
{
|
{
|
||||||
|
BOOL valid = YES;
|
||||||
if (_requestToken == nil) {
|
if (_requestToken == nil) {
|
||||||
if (requestToken == nil) {
|
if (requestToken == nil) {
|
||||||
return NO;
|
if (RCT_DEBUG) {
|
||||||
|
RCTLogError(@"Missing request token for request: %@", _request);
|
||||||
|
}
|
||||||
|
valid = NO;
|
||||||
}
|
}
|
||||||
_requestToken = requestToken;
|
_requestToken = requestToken;
|
||||||
}
|
} else if (![requestToken isEqual:_requestToken]) {
|
||||||
if (![requestToken isEqual:_requestToken]) {
|
|
||||||
if (RCT_DEBUG) {
|
if (RCT_DEBUG) {
|
||||||
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
|
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
|
||||||
}
|
}
|
||||||
|
valid = NO;
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
_status = RCTNetworkTaskFinished;
|
||||||
if (_completionBlock) {
|
if (_completionBlock) {
|
||||||
_completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0
|
_completionBlock(_response, nil, [NSError errorWithDomain:RCTErrorDomain code:0
|
||||||
userInfo:@{NSLocalizedDescriptionKey: @"Unrecognized request token."}]);
|
userInfo:@{NSLocalizedDescriptionKey: @"Invalid request token."}]);
|
||||||
}
|
}
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
_status = RCTNetworkTaskFinished;
|
|
||||||
return NO;
|
|
||||||
}
|
}
|
||||||
return YES;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
|
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
|
||||||
|
@ -132,11 +137,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||||
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
||||||
{
|
{
|
||||||
if ([self validateRequestToken:requestToken]) {
|
if ([self validateRequestToken:requestToken]) {
|
||||||
|
_status = RCTNetworkTaskFinished;
|
||||||
if (_completionBlock) {
|
if (_completionBlock) {
|
||||||
_completionBlock(_response, _data, error);
|
_completionBlock(_response, _data, error);
|
||||||
}
|
}
|
||||||
[self invalidate];
|
[self invalidate];
|
||||||
_status = RCTNetworkTaskFinished;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue