[Darwin] MTRDeviceConnectivityMonitor stopMonitoring crash fix (#33666)
* [Darwin] MTRDeviceConnectivityMonitor stopMonitoring crash fix
* Update src/darwin/Framework/CHIP/MTRDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm
index 4e916d1..98af8fb 100644
--- a/src/darwin/Framework/CHIP/MTRDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice.mm
@@ -395,8 +395,8 @@
NSMutableSet<MTRClusterPath *> * _persistedClusters;
// When we last failed to subscribe to the device (either via
- // _setupSubscription or via the auto-resubscribe behavior of the
- // ReadClient). Nil if we have had no such failures.
+ // _setupSubscriptionWithReason or via the auto-resubscribe behavior
+ // of the ReadClient). Nil if we have had no such failures.
NSDate * _Nullable _lastSubscriptionFailureTime;
MTRDeviceConnectivityMonitor * _connectivityMonitor;
@@ -745,10 +745,10 @@
if ([self _deviceUsesThread]) {
[self _scheduleSubscriptionPoolWork:^{
std::lock_guard lock(self->_lock);
- [self _setupSubscription];
+ [self _setupSubscriptionWithReason:@"delegate is set and scheduled subscription is happening"];
} inNanoseconds:0 description:@"MTRDevice setDelegate first subscription"];
} else {
- [self _setupSubscription];
+ [self _setupSubscriptionWithReason:@"delegate is set and subscription is needed"];
}
}
}
@@ -788,14 +788,14 @@
MTR_LOG("%@ saw new operational advertisement", self);
- [self _triggerResubscribeWithReason:"operational advertisement seen"
+ [self _triggerResubscribeWithReason:@"operational advertisement seen"
nodeLikelyReachable:YES];
}
// Trigger a resubscribe as needed. nodeLikelyReachable should be YES if we
// have reason to suspect the node is now reachable, NO if we have no idea
// whether it might be.
-- (void)_triggerResubscribeWithReason:(const char *)reason nodeLikelyReachable:(BOOL)nodeLikelyReachable
+- (void)_triggerResubscribeWithReason:(NSString *)reason nodeLikelyReachable:(BOOL)nodeLikelyReachable
{
assertChipStackLockedByCurrentThread();
@@ -814,7 +814,7 @@
// establish a CASE session. And at that point, our subscription will
// trigger the state change as needed.
if (self.reattemptingSubscription) {
- [self _reattemptSubscriptionNowIfNeeded];
+ [self _reattemptSubscriptionNowIfNeededWithReason:reason];
} else {
readClientToResubscribe = self->_currentReadClient;
subscriptionCallback = self->_currentSubscriptionCallback;
@@ -829,7 +829,7 @@
// here (e.g. still booting up), but should try again reasonably quickly.
subscriptionCallback->ResetResubscriptionBackoff();
}
- readClientToResubscribe->TriggerResubscribeIfScheduled(reason);
+ readClientToResubscribe->TriggerResubscribeIfScheduled(reason.UTF8String);
}
}
@@ -893,7 +893,7 @@
// ReadClient in there. If the dispatch fails, that's fine; it means our
// controller has shut down, so nothing to be done.
[_deviceController asyncDispatchToMatterQueue:^{
- [self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:NO];
+ [self _triggerResubscribeWithReason:@"read-through skipped while not subscribed" nodeLikelyReachable:NO];
}
errorHandler:nil];
}
@@ -1160,7 +1160,7 @@
// this block is run -- if other triggering events had happened, this would become a no-op.
auto resubscriptionBlock = ^{
[self->_deviceController asyncDispatchToMatterQueue:^{
- [self _triggerResubscribeWithReason:"ResubscriptionNeeded timer fired" nodeLikelyReachable:NO];
+ [self _triggerResubscribeWithReason:@"ResubscriptionNeeded timer fired" nodeLikelyReachable:NO];
} errorHandler:^(NSError * _Nonnull error) {
// If controller is not running, clear work item from the subscription queue
MTR_LOG_ERROR("%@ could not dispatch to matter queue for resubscription - error %@", self, error);
@@ -1235,17 +1235,17 @@
// If we started subscription or session establishment but failed, remove item from the subscription pool so we can re-queue.
[self _clearSubscriptionPoolWork];
- // Call _reattemptSubscriptionNowIfNeeded when timer fires - if subscription is
+ // Call _reattemptSubscriptionNowIfNeededWithReason when timer fires - if subscription is
// in a better state at that time this will be a no-op.
auto resubscriptionBlock = ^{
os_unfair_lock_lock(&self->_lock);
- [self _reattemptSubscriptionNowIfNeeded];
+ [self _reattemptSubscriptionNowIfNeededWithReason:@"got subscription reset"];
os_unfair_lock_unlock(&self->_lock);
};
int64_t resubscriptionDelayNs = static_cast<int64_t>(secondsToWait * NSEC_PER_SEC);
if ([self _deviceUsesThread]) {
- // For Thread-enabled devices, schedule the _reattemptSubscriptionNowIfNeeded call to run in the subscription pool
+ // For Thread-enabled devices, schedule the _reattemptSubscriptionNowIfNeededWithReason call to run in the subscription pool
[self _scheduleSubscriptionPoolWork:resubscriptionBlock inNanoseconds:resubscriptionDelayNs description:@"MTRDevice resubscription"];
} else {
// For non-Thread-enabled devices, just call the resubscription block after the specified time
@@ -1253,7 +1253,7 @@
}
}
-- (void)_reattemptSubscriptionNowIfNeeded
+- (void)_reattemptSubscriptionNowIfNeededWithReason:(NSString *)reason
{
os_unfair_lock_assert_owner(&self->_lock);
if (!self.reattemptingSubscription) {
@@ -1262,7 +1262,7 @@
MTR_LOG("%@ reattempting subscription", self);
self.reattemptingSubscription = NO;
- [self _setupSubscription];
+ [self _setupSubscriptionWithReason:reason];
}
- (void)_handleUnsolicitedMessageFromPublisher
@@ -1284,8 +1284,8 @@
// reestablishment, this starts the attempt right away
// TODO: This doesn't really make sense. If we _don't_ have a live
// ReadClient how did we get this notification and if we _do_ have an active
- // ReadClient, this call or _setupSubscription would be no-ops.
- [self _reattemptSubscriptionNowIfNeeded];
+ // ReadClient, this call or _setupSubscriptionWithReason would be no-ops.
+ [self _reattemptSubscriptionNowIfNeededWithReason:@"got unsolicited message from publisher"];
}
- (void)_markDeviceAsUnreachableIfNeverSubscribed
@@ -1941,7 +1941,7 @@
self->_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithCompressedFabricID:compressedFabricID nodeID:self.nodeID];
[self->_connectivityMonitor startMonitoringWithHandler:^{
[self->_deviceController asyncDispatchToMatterQueue:^{
- [self _triggerResubscribeWithReason:"device connectivity changed" nodeLikelyReachable:YES];
+ [self _triggerResubscribeWithReason:@"device connectivity changed" nodeLikelyReachable:YES];
}
errorHandler:nil];
} queue:self.queue];
@@ -1959,12 +1959,12 @@
}
// assume lock is held
-- (void)_setupSubscription
+- (void)_setupSubscriptionWithReason:(NSString *)reason
{
os_unfair_lock_assert_owner(&self->_lock);
if (![self _subscriptionsAllowed]) {
- MTR_LOG("_setupSubscription: Subscriptions not allowed. Do not set up subscription");
+ MTR_LOG("%@ _setupSubscription: Subscriptions not allowed. Do not set up subscription", self);
return;
}
@@ -1986,6 +1986,8 @@
[self _changeInternalState:MTRInternalDeviceStateSubscribing];
+ MTR_LOG("%@ setting up subscription with reason: %@", self, reason);
+
// Set up a timer to mark as not reachable if it takes too long to set up a subscription
MTRWeakReference<MTRDevice *> * weakSelf = [MTRWeakReference weakReferenceWithObject:self];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription) * static_cast<int64_t>(NSEC_PER_SEC)), self.queue, ^{
@@ -3512,7 +3514,7 @@
MTR_LOG("%@ _deviceMayBeReachable called", self);
- [self _triggerResubscribeWithReason:"SPI client indicated the device may now be reachable"
+ [self _triggerResubscribeWithReason:@"SPI client indicated the device may now be reachable"
nodeLikelyReachable:YES];
}
diff --git a/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm
index 3df270b..6a9ac60 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm
@@ -258,7 +258,7 @@
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kSharedConnectionLingerIntervalSeconds * NSEC_PER_SEC), sSharedResolverQueue, ^{
std::lock_guard lock(sConnectivityMonitorLock);
- if (!sConnectivityMonitorCount) {
+ if (!sConnectivityMonitorCount && sSharedResolverConnection) {
MTR_LOG("MTRDeviceConnectivityMonitor: Closing shared resolver connection");
DNSServiceRefDeallocate(sSharedResolverConnection);
sSharedResolverConnection = NULL;
@@ -271,9 +271,14 @@
- (void)stopMonitoring
{
+ MTR_LOG("%@ stop connectivity monitoring for %@", self, self->_instanceName);
+ std::lock_guard lock(sConnectivityMonitorLock);
+ if (!sSharedResolverConnection || !sSharedResolverQueue) {
+ MTR_LOG("%@ shared resolver connection already stopped - nothing to do", self);
+ }
+
// DNSServiceRefDeallocate must be called on the same queue set on the shared connection.
dispatch_async(sSharedResolverQueue, ^{
- MTR_LOG("%@ stop connectivity monitoring for %@", self, self->_instanceName);
std::lock_guard lock(sConnectivityMonitorLock);
[self _stopMonitoring];
});