robert_williams_ditto, you posted 5 crash reports. Of those, the first four look related to this. The fifth is something different entirely. It doesn’t even mention stream in the crash report.
That's my mistake. Let's just focus on the first 4 crash reports.
That crash report has two threads that reference CFStream. Thread 19 is the crashing thread, much like in pouya176’s case. But thread 9 is interesting. It suggests you’re using CFStream for Bluetooth. Given that this is iOS, the only way I can see that making sense is if you’re using External Accessory framework to talk to an MFi accessory. Is that right?
We use Bluetooth (specifically BLE) to communicate with other iOS devices. After establishing a connection we open an L2CAP channel which provides an input stream and an output stream. So we don't use CFStream directly, but the CBL2CAPChannel type from CoreBluetooth does appear to use CFStream in its implementation.
Thank you for calling my attention to thread 9 in the first crash report. It is actively closing the L2CAP channel at the time of the crash:
Thread 9:
0 libsystem_kernel.dylib 0x00000001d550eb18 __psynch_mutexwait + 8 (:-1)
1 libsystem_pthread.dylib 0x000000020ca672f4 _pthread_mutex_firstfit_lock_wait + 84 (pthread_mutex.c:1414)
2 libsystem_pthread.dylib 0x000000020ca66d08 _pthread_mutex_firstfit_lock_slow + 220 (pthread_mutex.c:1490)
3 CoreFoundation 0x00000001856cd28c _CFStreamClose + 476 (CFStream.c:312)
4 DittoObjC 0x0000000107df2274 -[DITL2CAPConnection close] + 88 (DITL2CAPConnection.m:101)
5 DittoObjC 0x0000000107defe18 -[DITBluetoothPlatform disconnectFromPeripheral:] + 136 (DITBluetoothPlatform.m:980)
6 DittoObjC 0x0000000107debc0c ble_disconnect_peripheral_cb + 84 (DITBluetoothPlatform.m:92)
7 DittoObjC 0x000000010809bdf4 ditto_mesh::ble::client_transport::BleClientPeerTransport::request_disconnect::ha7af5922ecc95e18 + 192
8 DittoObjC 0x00000001075d4694 ditto_mesh::ble::client_transport::BleClientRemotePeer::connect_handshake::_$u7b$$u7b$closure$u7d$$u7d$::hbf7382db8871e8b6 + 11428
I wonder if there may be a race condition, because the CFStream is being closed while it is still in use on another thread. It would be very interesting to confirm whether the lines of code in CoreFoundation where the crash occurs are using resources that are being freed/deallocated on the other thread:
Thread 19 Crashed:
0 CoreFoundation 0x0000000185680e54 CF_IS_OBJC + 76 (CFRuntime.c:461)
1 CoreFoundation 0x000000018567ad6c CFRetain + 64 (CFRuntime.c:1169)
2 CoreFoundation 0x0000000185731540 _signalEventSync + 92 (CFStream.c:609)
3 CoreFoundation 0x000000018573146c _cfstream_shared_signalEventSync + 392 (CFStream.c:757)
...
So far, I have been unable to confirm my theory because I can't yet reproduce the crash myself. I have only received these crash reports from our customers who use our SDK. Do you have any ideas about how to force this crash to occur so that I can confirm my hypothesis?
For context, here is our code that closes an L2CAP channel:
- (void)close {
// Closing streams on two threads can race and cause a crash internal to NSStream (#3696)
// This gets called both when handling stream failures and when we want to deliberately
// disconnect, and we deliberately disconnect on failure, just to make sure.
@synchronized (self) {
[_channel.inputStream setDelegate:nil];
[_channel.inputStream close]; // line 101
[_channel.inputStream removeFromRunLoop:self.runLoop forMode:NSDefaultRunLoopMode];
[_channel.outputStream setDelegate:nil];
[_channel.outputStream close]; // line 104
[_channel.outputStream removeFromRunLoop:self.runLoop forMode:NSDefaultRunLoopMode];
}
}
- (void)dealloc {
[self close];
} // line 111
(I included some line numbers because they are referenced in the first, third, and fourth crash reports)
And here is our code that sets up an L2CAP channel:
- (instancetype)initWithChannel:(CBL2CAPChannel *)channel handle:(DITTransportHandleWrapper *)handle isServer:(BOOL)isServer uuid:(NSUUID *)uuid runLoop:(NSRunLoop *)runLoop {
self = [super init];
if (self) {
_channel = channel;
_runLoop = runLoop;
_handle = handle;
_isServer = isServer;
_uuid = uuid;
_isFailed = NO;
DITStreamWeakAdaptor *inputAdaptor = [[DITStreamWeakAdaptor alloc] initWithTrueDelegate:self];
DITStreamWeakAdaptor *outputAdaptor = [[DITStreamWeakAdaptor alloc] initWithTrueDelegate:self];
[channel.inputStream setDelegate:inputAdaptor];
[channel.outputStream setDelegate:outputAdaptor];
// NOTE: we've run into multiple race conditions due to this connection
// being deallocated while the channel is active. To avoid a whole class
// of problems, we make sure this delegate remains alive until the
// channel's streams are properly closed.
objc_setAssociatedObject(channel.inputStream, (__bridge void *)self, inputAdaptor, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(channel.outputStream, (__bridge void *)self, outputAdaptor, OBJC_ASSOCIATION_RETAIN);
[channel.inputStream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[channel.outputStream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[channel.inputStream open];
[channel.outputStream open];
}
return self;
}
Thank you for your help!