react-native/Libraries/Network/RCTHTTPRequestHandler.mm

169 lines
5.1 KiB
Plaintext
Raw Normal View History

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTHTTPRequestHandler.h"
#import <mutex>
#import "RCTNetworking.h"
@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate>
@end
@implementation RCTHTTPRequestHandler
{
NSMapTable *_delegates;
NSURLSession *_session;
std::mutex _mutex;
}
@synthesize bridge = _bridge;
Fix RCTImageLoader multi thread crash (#22746) Summary: Fix crash similar to #22410 react-native: 0.51.0 react: 16.0.0 Changelog: ---------- [iOS] [Changed] - Use onw serial queue to execute invalidate and send request action in RCTHTTPRequestHandler.mm. Message: -------- ``` - (void)invalidate { [_session invalidateAndCancel]; _session = nil; } - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate { // Lazy setup if (!_session && [self isValid]) { NSOperationQueue *callbackQueue = [NSOperationQueue new]; callbackQueue.maxConcurrentOperationCount = 1; callbackQueue.underlyingQueue = [[_bridge networking] methodQueue]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; [configuration setHTTPShouldSetCookies:YES]; [configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; [configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue]; std::lock_guard<std::mutex> lock(_mutex); _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:0]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; { std::lock_guard<std::mutex> lock(_mutex); [_delegates setObject:delegate forKey:task]; } [task resume]; return task; } ``` now the invalidate function is called by the RCTBridge.invalidate->RCTCxxBridge.invalidate->[moduleData.instance invalidate] , this is on the "com.facebook.react.HTTPRequestHandlerQueue". the sendRequest:withDelegate function is called by RCTImageLoader and is on the "com.facebook.react.imageLoaderURLRequestQueue". when one thread step in invalidate and execute [_session invalidateAndCancel] and the another thread step in sendRequest:withDelegate and execute [_session dataTaskWithRequest:request], the _session is invalidate, so there will be a crash "Task created in a session that has been invalidated" Pull Request resolved: https://github.com/facebook/react-native/pull/22746 Differential Revision: D13781512 Pulled By: cpojer fbshipit-source-id: bd5fd1edf593e2bcdcc18596a29e906882bac8a4
2019-01-23 02:39:51 -08:00
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)invalidate
{
Fix RCTImageLoader multi thread crash (#22746) Summary: Fix crash similar to #22410 react-native: 0.51.0 react: 16.0.0 Changelog: ---------- [iOS] [Changed] - Use onw serial queue to execute invalidate and send request action in RCTHTTPRequestHandler.mm. Message: -------- ``` - (void)invalidate { [_session invalidateAndCancel]; _session = nil; } - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate { // Lazy setup if (!_session && [self isValid]) { NSOperationQueue *callbackQueue = [NSOperationQueue new]; callbackQueue.maxConcurrentOperationCount = 1; callbackQueue.underlyingQueue = [[_bridge networking] methodQueue]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; [configuration setHTTPShouldSetCookies:YES]; [configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; [configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue]; std::lock_guard<std::mutex> lock(_mutex); _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:0]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; { std::lock_guard<std::mutex> lock(_mutex); [_delegates setObject:delegate forKey:task]; } [task resume]; return task; } ``` now the invalidate function is called by the RCTBridge.invalidate->RCTCxxBridge.invalidate->[moduleData.instance invalidate] , this is on the "com.facebook.react.HTTPRequestHandlerQueue". the sendRequest:withDelegate function is called by RCTImageLoader and is on the "com.facebook.react.imageLoaderURLRequestQueue". when one thread step in invalidate and execute [_session invalidateAndCancel] and the another thread step in sendRequest:withDelegate and execute [_session dataTaskWithRequest:request], the _session is invalidate, so there will be a crash "Task created in a session that has been invalidated" Pull Request resolved: https://github.com/facebook/react-native/pull/22746 Differential Revision: D13781512 Pulled By: cpojer fbshipit-source-id: bd5fd1edf593e2bcdcc18596a29e906882bac8a4
2019-01-23 02:39:51 -08:00
dispatch_async(self->_methodQueue, ^{
[self->_session invalidateAndCancel];
self->_session = nil;
});
}
- (BOOL)isValid
{
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 03:09:00 -08:00
// if session == nil and delegates != nil, we've been invalidated
return _session || !_delegates;
}
#pragma mark - NSURLRequestHandler
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
static NSSet<NSString *> *schemes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// technically, RCTHTTPRequestHandler can handle file:// as well,
// but it's less efficient than using RCTFileRequestHandler
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
});
return [schemes containsObject:request.URL.scheme.lowercaseString];
}
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_session && [self isValid]) {
NSOperationQueue *callbackQueue = [NSOperationQueue new];
callbackQueue.maxConcurrentOperationCount = 1;
callbackQueue.underlyingQueue = [[_bridge networking] methodQueue];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setHTTPShouldSetCookies:YES];
[configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
_session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:callbackQueue];
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 03:09:00 -08:00
std::lock_guard<std::mutex> lock(_mutex);
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 03:09:00 -08:00
_delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory
capacity:0];
}
Fix RCTImageLoader multi thread crash (#22746) Summary: Fix crash similar to #22410 react-native: 0.51.0 react: 16.0.0 Changelog: ---------- [iOS] [Changed] - Use onw serial queue to execute invalidate and send request action in RCTHTTPRequestHandler.mm. Message: -------- ``` - (void)invalidate { [_session invalidateAndCancel]; _session = nil; } - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate { // Lazy setup if (!_session && [self isValid]) { NSOperationQueue *callbackQueue = [NSOperationQueue new]; callbackQueue.maxConcurrentOperationCount = 1; callbackQueue.underlyingQueue = [[_bridge networking] methodQueue]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; [configuration setHTTPShouldSetCookies:YES]; [configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; [configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:callbackQueue]; std::lock_guard<std::mutex> lock(_mutex); _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:0]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; { std::lock_guard<std::mutex> lock(_mutex); [_delegates setObject:delegate forKey:task]; } [task resume]; return task; } ``` now the invalidate function is called by the RCTBridge.invalidate->RCTCxxBridge.invalidate->[moduleData.instance invalidate] , this is on the "com.facebook.react.HTTPRequestHandlerQueue". the sendRequest:withDelegate function is called by RCTImageLoader and is on the "com.facebook.react.imageLoaderURLRequestQueue". when one thread step in invalidate and execute [_session invalidateAndCancel] and the another thread step in sendRequest:withDelegate and execute [_session dataTaskWithRequest:request], the _session is invalidate, so there will be a crash "Task created in a session that has been invalidated" Pull Request resolved: https://github.com/facebook/react-native/pull/22746 Differential Revision: D13781512 Pulled By: cpojer fbshipit-source-id: bd5fd1edf593e2bcdcc18596a29e906882bac8a4
2019-01-23 02:39:51 -08:00
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self->_methodQueue, ^{
task = [self->_session dataTaskWithRequest:request];
});
{
std::lock_guard<std::mutex> lock(_mutex);
[_delegates setObject:delegate forKey:task];
}
[task resume];
return task;
}
- (void)cancelRequest:(NSURLSessionDataTask *)task
{
{
std::lock_guard<std::mutex> lock(_mutex);
[_delegates removeObjectForKey:task];
}
[task cancel];
}
#pragma mark - NSURLSession delegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didSendDataWithProgress:totalBytesSent];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
// Reset the cookies on redirect.
// This is necessary because we're not letting iOS handle cookies by itself
NSMutableURLRequest *nextRequest = [request mutableCopy];
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
nextRequest.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
completionHandler(nextRequest);
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didReceiveResponse:response];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)task
didReceiveData:(NSData *)data
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
}
[delegate URLRequest:task didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
id<RCTURLRequestDelegate> delegate;
{
std::lock_guard<std::mutex> lock(_mutex);
delegate = [_delegates objectForKey:task];
[_delegates removeObjectForKey:task];
}
[delegate URLRequest:task didCompleteWithError:error];
}
@end