Skip to content

Commit

Permalink
Merge pull request #33 from dumganhar/fix/SRWebSocket-memory-leak
Browse files Browse the repository at this point in the history
Memory leak fix and cancel connection timeout timer in scheduleCleanup
  • Loading branch information
nantas authored Jul 20, 2017
2 parents 87575ef + 4eb591a commit b43a5b3
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 22 deletions.
4 changes: 0 additions & 4 deletions sources/SocketRocket/Internal/Delegate/SRDelegateController.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ typedef void(^SRDelegateBlock)(id<SRWebSocketDelegate> _Nullable delegate, SRDel
@property (nonatomic, weak) id<SRWebSocketDelegate> delegate;
@property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods;

#if OS_OBJECT_USE_OBJC
@property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue;
#else
@property (nullable, nonatomic, assign) dispatch_queue_t dispatchQueue;
#endif
@property (nullable, nonatomic, strong) NSOperationQueue *operationQueue;

///--------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions sources/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@
unmaskBytes:(BOOL)unmaskBytes;
- (void)returnConsumer:(SRIOConsumer *)consumer;

- (void)clear;

@end
6 changes: 6 additions & 0 deletions sources/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ - (void)returnConsumer:(SRIOConsumer *)consumer;
}
}

-(void)clear
{
_poolSize = 0;
_bufferedConsumers = nil;
}

@end
8 changes: 4 additions & 4 deletions sources/SocketRocket/Internal/Proxy/SRProxyConnect.m
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ - (void)_fetchPAC:(NSURL *)PACurl withProxySettings:(NSDictionary *)proxySetting
[self _openConnection];
return;
}
__weak __typeof__(self) wself = self;
__weak __typeof(self) wself = self;
NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
__strong __typeof__(wself) sself = wself;
__strong __typeof(wself) sself = wself;
if (!error) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[sself _runPACScript:script withProxySettings:proxySettings];
Expand Down Expand Up @@ -454,9 +454,9 @@ - (void)_writeData:(NSData *)data
{
const uint8_t * bytes = data.bytes;
__block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up
__weak __typeof__(self) wself = self;
__weak __typeof(self) wself = self;
dispatch_async(_writeQueue, ^{
__strong __typeof__(wself) sself = self;
__strong __typeof(wself) sself = self;
if (!sself) {
return;
}
Expand Down
49 changes: 35 additions & 14 deletions sources/SocketRocket/SRWebSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ + (BOOL)automaticallyNotifiesObserversOfReadyState {
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
///--------------------------------------
Expand All @@ -307,18 +315,12 @@ - (void)open
_selfRetain = self;

if (_urlRequest.timeoutInterval > 0) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_urlRequest.timeoutInterval * NSEC_PER_SEC));
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];
}
});
[self performSelector:@selector(_onTimeout) withObject:nil afterDelay:_urlRequest.timeoutInterval];
}

_proxyConnect = [[SRProxyConnect alloc] initWithURL:_url];

__weak __typeof__(self) wself = self;
__weak __typeof(self) wself = self;
[_proxyConnect openNetworkStreamWithCompletion:^(NSError *error, NSInputStream *readStream, NSOutputStream *writeStream) {
[wself _connectionDoneWithError:error readStream:readStream writeStream:writeStream];
}];
Expand Down Expand Up @@ -419,14 +421,23 @@ - (void)_readHTTPHeader;
_receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
}

// Uses weak self object in the block, otherwise Consumers will retain SRWebSocket instance,
// and SRWebSocket instance also hold consumers, cycle reference will occur.
__weak __typeof(self) wself = self;

[self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) {
CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);

if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
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 {
[self _readHTTPHeader];
[sself _readHTTPHeader];
}
}];
}
Expand Down Expand Up @@ -1127,6 +1138,16 @@ - (void)_scheduleCleanup

_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:
// 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];
Expand Down Expand Up @@ -1392,7 +1413,7 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
__weak __typeof__(self) wself = self;
__weak __typeof(self) wself = self;

if (_requestRequiresSSL && !_streamSecurityValidated &&
(eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) {
Expand Down

0 comments on commit b43a5b3

Please sign in to comment.