Memory leak fix and cancel connection timeout timer in scheduleCleanup

This commit is contained in:
James Chen 2017-07-20 15:50:06 +08:00
parent 87575efb54
commit 4eb591a607
5 changed files with 48 additions and 23 deletions

View File

@ -33,11 +33,7 @@ typedef void(^SRDelegateBlock)(id<SRWebSocketDelegate> _Nullable delegate, SRDel
@property (nonatomic, weak) id<SRWebSocketDelegate> delegate; @property (nonatomic, weak) id<SRWebSocketDelegate> delegate;
@property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods; @property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods;
#if OS_OBJECT_USE_OBJC
@property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue; @property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue;
#else
@property (nullable, nonatomic, assign) dispatch_queue_t dispatchQueue;
#endif
@property (nullable, nonatomic, strong) NSOperationQueue *operationQueue; @property (nullable, nonatomic, strong) NSOperationQueue *operationQueue;
///-------------------------------------- ///--------------------------------------

View File

@ -25,4 +25,6 @@
unmaskBytes:(BOOL)unmaskBytes; unmaskBytes:(BOOL)unmaskBytes;
- (void)returnConsumer:(SRIOConsumer *)consumer; - (void)returnConsumer:(SRIOConsumer *)consumer;
- (void)clear;
@end @end

View File

@ -61,4 +61,10 @@
} }
} }
-(void)clear
{
_poolSize = 0;
_bufferedConsumers = nil;
}
@end @end

View File

@ -234,11 +234,11 @@
[self _openConnection]; [self _openConnection];
return; return;
} }
__weak __typeof__(self) wself = self; __weak __typeof(self) wself = self;
NSURLRequest *request = [NSURLRequest requestWithURL:PACurl]; NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
NSURLSession *session = [NSURLSession sharedSession]; NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
__strong __typeof__(wself) sself = wself; __strong __typeof(wself) sself = wself;
if (!error) { if (!error) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[sself _runPACScript:script withProxySettings:proxySettings]; [sself _runPACScript:script withProxySettings:proxySettings];
@ -454,9 +454,9 @@ static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0;
{ {
const uint8_t * bytes = data.bytes; const uint8_t * bytes = data.bytes;
__block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up __block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up
__weak __typeof__(self) wself = self; __weak __typeof(self) wself = self;
dispatch_async(_writeQueue, ^{ dispatch_async(_writeQueue, ^{
__strong __typeof__(wself) sself = self; __strong __typeof(wself) sself = self;
if (!sself) { if (!sself) {
return; return;
} }

View File

@ -295,6 +295,14 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
return NO; return NO;
} }
-(void)_onTimeout
{
if (self.readyState == SR_CONNECTING) {
NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server.");
[self _failWithError:error];
}
}
///-------------------------------------- ///--------------------------------------
#pragma mark - Open / Close #pragma mark - Open / Close
///-------------------------------------- ///--------------------------------------
@ -307,18 +315,12 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
_selfRetain = self; _selfRetain = self;
if (_urlRequest.timeoutInterval > 0) { if (_urlRequest.timeoutInterval > 0) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_urlRequest.timeoutInterval * NSEC_PER_SEC)); [self performSelector:@selector(_onTimeout) withObject:nil afterDelay:_urlRequest.timeoutInterval];
dispatch_after(popTime, dispatch_get_main_queue(), ^{
if (self.readyState == SR_CONNECTING) {
NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server.");
[self _failWithError:error];
}
});
} }
_proxyConnect = [[SRProxyConnect alloc] initWithURL:_url]; _proxyConnect = [[SRProxyConnect alloc] initWithURL:_url];
__weak __typeof__(self) wself = self; __weak __typeof(self) wself = self;
[_proxyConnect openNetworkStreamWithCompletion:^(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream) { [_proxyConnect openNetworkStreamWithCompletion:^(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream) {
[wself _connectionDoneWithError:error readStream:readStream writeStream:writeStream]; [wself _connectionDoneWithError:error readStream:readStream writeStream:writeStream];
}]; }];
@ -419,14 +421,23 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
_receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
} }
[self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) { // Uses weak self object in the block, otherwise Consumers will retain SRWebSocket instance,
CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); // and SRWebSocket instance also hold consumers, cycle reference will occur.
__weak __typeof(self) wself = self;
if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) {
SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
[self _HTTPHeadersDidFinish]; __strong __typeof(wself) sself = wself;
if (sself == nil)
return;
CFHTTPMessageAppendBytes(sself.receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
if (CFHTTPMessageIsHeaderComplete(sself.receivedHTTPHeaders)) {
SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(sself.receivedHTTPHeaders)));
[sself _HTTPHeadersDidFinish];
} else { } else {
[self _readHTTPHeader]; [sself _readHTTPHeader];
} }
}]; }];
} }
@ -1127,6 +1138,16 @@ static const uint8_t SRPayloadLenMask = 0x7F;
_cleanupScheduled = YES; _cleanupScheduled = YES;
// _consumers retain SRWebSocket instance by block copy, if there are consumers here, clear them.
[_consumers removeAllObjects];
[_consumerPool clear];
// Cancel the timer which retains SRWebSocket instance.
// If we don't cancel the timer, the 'dealloc' method will be invoked only after the time (default: 60s) have come, which may cause memory increase.
dispatch_async(dispatch_get_main_queue(), ^(){
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_onTimeout) object:nil];
});
// Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves:
// This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc
NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO];
@ -1392,7 +1413,7 @@ static const size_t SRFrameHeaderOverhead = 32;
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{ {
__weak __typeof__(self) wself = self; __weak __typeof(self) wself = self;
if (_requestRequiresSSL && !_streamSecurityValidated && if (_requestRequiresSSL && !_streamSecurityValidated &&
(eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) {