MYNetwork
MYNetwork copied to clipboard
Fix for TCPConnection/TCPStream Relationship ARC Crash
I spent some time tracking this down (since I wasn't happy with the solution other contributors had suggested (see #9 ), and I believe I understand the issue and implications.
Specifically:
-
TCPConnection
adds newly open connections to an internal static arraysAllConnections
which is the only thing preventing ARC from deallocating the instances. -
TCPConnection
maintains astrong
_reader
and_writer
(bothTCPStream
objects) which in turn also havestrong
reference to the connection. - When a
TCPStream
object receives an EOF it callsTCPConnection -_streamGotEOF:
withself
to inform its connection to disconnect. This ultimately causes the connection to calldisconnect
on both its_reader
and_writer
streams via_streamCanClose:
. -
TCPStream -disconnect
callsTCPConnection -_streamDisconnected:
withself
to let its connection know it is closed. - Once both of the
TCPConnection
streams are closed, the connection calls_closed
which removes itself from the static arraysAllConnections
, causing it to be deallocated. - At this point any subsequent calls to the
TCPConnection
instance are to a deallocated instance and a crash occurs.
This all manifests itself in the TCPConnection.m
_streamGotEOF:
method.
My fix is to strongly reference self
so self
is available in the current scope. This is similar to what MYDeferDealloc
does in a non-ARC setting.
Proposed fix:
TCPConnection.m
- (void) _streamGotEOF: (TCPStream*)stream
{
LogTo(TCP,@"%@ got EOF on %@",self,stream);
[stream disconnect];
if( _status == kTCP_Closing ) {
// Strongly reference `self` in this scope, so when `_closed` (ultimately called by `_streamCanClose:`) removes `self` from `sAllConnections` we are not immediately deallocated (and subsequently crash when attempting to call `[self _checkIfClosed]`)
__strong typeof(self) sSelf = self;
[sSelf _streamCanClose: stream];
[sSelf _checkIfClosed];
} else {
[self _stream: stream
gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
}
}