Skip to content

Commit 3f0f242

Browse files
Ensure that all MTRDevice state/internalState changes happen on the Matter queue. (project-chip#35490)
This avoids races where we queue blocks to different queues that both try to change the state, which were resulting in non-deterministic final state. Fixes project-chip#34796
1 parent 2e5b709 commit 3f0f242

File tree

1 file changed

+89
-46
lines changed

1 file changed

+89
-46
lines changed

src/darwin/Framework/CHIP/MTRDevice_Concrete.mm

+89-46
Original file line numberDiff line numberDiff line change
@@ -757,11 +757,16 @@ - (void)_ensureSubscriptionForExistingDelegates:(NSString *)reason
757757
if ([self _deviceUsesThread]) {
758758
MTR_LOG(" => %@ - device is a thread device, scheduling in pool", self);
759759
[self _scheduleSubscriptionPoolWork:^{
760-
std::lock_guard lock(self->_lock);
761-
[self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and scheduled subscription is happening", reason]];
760+
[self->_deviceController asyncDispatchToMatterQueue:^{
761+
std::lock_guard lock(self->_lock);
762+
[self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and scheduled subscription is happening", reason]];
763+
} errorHandler:nil];
762764
} inNanoseconds:0 description:@"MTRDevice setDelegate first subscription"];
763765
} else {
764-
[self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and subscription is needed", reason]];
766+
[_deviceController asyncDispatchToMatterQueue:^{
767+
std::lock_guard lock(self->_lock);
768+
[self _setupSubscriptionWithReason:[NSString stringWithFormat:@"%@ and subscription is needed", reason]];
769+
} errorHandler:nil];
765770
}
766771
}
767772

@@ -946,6 +951,15 @@ - (void)_callDelegateDeviceCachePrimed
946951
// assume lock is held
947952
- (void)_changeState:(MTRDeviceState)state
948953
{
954+
// We want to avoid situations where something changes our state and then an
955+
// async block that was queued earlier in response to something changes it
956+
// again, to a value that no longer makes sense. To avoid that:
957+
//
958+
// 1) All state changes happen on the Matter queue.
959+
// 2) All state changes happen synchronously with the event that actually
960+
// triggers the state change.
961+
assertChipStackLockedByCurrentThread();
962+
949963
os_unfair_lock_assert_owner(&self->_lock);
950964
MTRDeviceState lastState = _state;
951965
_state = state;
@@ -970,6 +984,15 @@ - (void)_changeState:(MTRDeviceState)state
970984

971985
- (void)_changeInternalState:(MTRInternalDeviceState)state
972986
{
987+
// We want to avoid situations where something changes our state and then an
988+
// async block that was queued earlier in response to something changes it
989+
// again, to a value that no longer makes sense. To avoid that:
990+
//
991+
// 1) All state changes happen on the Matter queue.
992+
// 2) All state changes happen synchronously with the event that actually
993+
// triggers the state change.
994+
assertChipStackLockedByCurrentThread();
995+
973996
os_unfair_lock_assert_owner(&self->_lock);
974997
MTRInternalDeviceState lastState = _internalDeviceState;
975998
_internalDeviceState = state;
@@ -1053,12 +1076,16 @@ - (void)_handleSubscriptionEstablished
10531076

10541077
- (void)_handleSubscriptionError:(NSError *)error
10551078
{
1079+
assertChipStackLockedByCurrentThread();
1080+
10561081
std::lock_guard lock(_lock);
10571082
[self _doHandleSubscriptionError:error];
10581083
}
10591084

10601085
- (void)_doHandleSubscriptionError:(NSError *)error
10611086
{
1087+
assertChipStackLockedByCurrentThread();
1088+
10621089
os_unfair_lock_assert_owner(&_lock);
10631090

10641091
[self _changeInternalState:MTRInternalDeviceStateUnsubscribed];
@@ -1174,13 +1201,23 @@ - (void)_scheduleSubscriptionPoolWork:(dispatch_block_t)workBlock inNanoseconds:
11741201

11751202
- (void)_handleResubscriptionNeededWithDelay:(NSNumber *)resubscriptionDelayMs
11761203
{
1177-
BOOL deviceUsesThread;
1204+
assertChipStackLockedByCurrentThread();
11781205

1179-
os_unfair_lock_lock(&self->_lock);
1206+
std::lock_guard lock(_lock);
11801207

1208+
// Change our state before going async.
11811209
[self _changeState:MTRDeviceStateUnknown];
11821210
[self _changeInternalState:MTRInternalDeviceStateResubscribing];
11831211

1212+
dispatch_async(self.queue, ^{
1213+
[self _handleResubscriptionNeededWithDelayOnDeviceQueue:resubscriptionDelayMs];
1214+
});
1215+
}
1216+
1217+
- (void)_handleResubscriptionNeededWithDelayOnDeviceQueue:(NSNumber *)resubscriptionDelayMs
1218+
{
1219+
os_unfair_lock_lock(&self->_lock);
1220+
11841221
// If we are here, then the ReadClient either just detected a subscription
11851222
// drop or just tried again and failed. Either way, count it as "tried and
11861223
// failed to subscribe": in the latter case it's actually true, and in the
@@ -1192,7 +1229,7 @@ - (void)_handleResubscriptionNeededWithDelay:(NSNumber *)resubscriptionDelayMs
11921229
_lastSubscriptionFailureTimeForDescription = _lastSubscriptionFailureTime;
11931230
}
11941231
[self _notifyDelegateOfPrivateInternalPropertiesChanges];
1195-
deviceUsesThread = [self _deviceUsesThread];
1232+
BOOL deviceUsesThread = [self _deviceUsesThread];
11961233

11971234
// If a previous resubscription failed, remove the item from the subscription pool.
11981235
[self _clearSubscriptionPoolWork];
@@ -1228,6 +1265,8 @@ - (void)_handleResubscriptionNeededWithDelay:(NSNumber *)resubscriptionDelayMs
12281265

12291266
- (void)_handleSubscriptionReset:(NSNumber * _Nullable)retryDelay
12301267
{
1268+
assertChipStackLockedByCurrentThread();
1269+
12311270
std::lock_guard lock(_lock);
12321271
[self _doHandleSubscriptionReset:retryDelay];
12331272
}
@@ -1247,6 +1286,8 @@ - (void)_setLastSubscriptionAttemptWait:(uint32_t)lastSubscriptionAttemptWait
12471286

12481287
- (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay
12491288
{
1289+
assertChipStackLockedByCurrentThread();
1290+
12501291
os_unfair_lock_assert_owner(&_lock);
12511292

12521293
if (_deviceController.isSuspended) {
@@ -1309,8 +1350,11 @@ - (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay
13091350
// Call _reattemptSubscriptionNowIfNeededWithReason when timer fires - if subscription is
13101351
// in a better state at that time this will be a no-op.
13111352
auto resubscriptionBlock = ^{
1312-
std::lock_guard lock(self->_lock);
1313-
[self _reattemptSubscriptionNowIfNeededWithReason:@"got subscription reset"];
1353+
[self->_deviceController asyncDispatchToMatterQueue:^{
1354+
std::lock_guard lock(self->_lock);
1355+
[self _reattemptSubscriptionNowIfNeededWithReason:@"got subscription reset"];
1356+
}
1357+
errorHandler:nil];
13141358
};
13151359

13161360
int64_t resubscriptionDelayNs = static_cast<int64_t>(secondsToWait * NSEC_PER_SEC);
@@ -1326,6 +1370,8 @@ - (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay
13261370

13271371
- (void)_reattemptSubscriptionNowIfNeededWithReason:(NSString *)reason
13281372
{
1373+
assertChipStackLockedByCurrentThread();
1374+
13291375
os_unfair_lock_assert_owner(&self->_lock);
13301376
if (!self.reattemptingSubscription) {
13311377
return;
@@ -1338,6 +1384,8 @@ - (void)_reattemptSubscriptionNowIfNeededWithReason:(NSString *)reason
13381384

13391385
- (void)_handleUnsolicitedMessageFromPublisher
13401386
{
1387+
assertChipStackLockedByCurrentThread();
1388+
13411389
std::lock_guard lock(_lock);
13421390

13431391
[self _changeState:MTRDeviceStateReachable];
@@ -1358,18 +1406,23 @@ - (void)_handleUnsolicitedMessageFromPublisher
13581406

13591407
- (void)_markDeviceAsUnreachableIfNeverSubscribed
13601408
{
1361-
os_unfair_lock_assert_owner(&self->_lock);
1409+
[_deviceController asyncDispatchToMatterQueue:^{
1410+
std::lock_guard lock(self->_lock);
13621411

1363-
if (HadSubscriptionEstablishedOnce(_internalDeviceState)) {
1364-
return;
1365-
}
1412+
if (HadSubscriptionEstablishedOnce(self->_internalDeviceState)) {
1413+
return;
1414+
}
13661415

1367-
MTR_LOG("%@ still not subscribed, marking the device as unreachable", self);
1368-
[self _changeState:MTRDeviceStateUnreachable];
1416+
MTR_LOG("%@ still not subscribed, marking the device as unreachable", self);
1417+
[self _changeState:MTRDeviceStateUnreachable];
1418+
}
1419+
errorHandler:nil];
13691420
}
13701421

13711422
- (void)_handleReportBegin
13721423
{
1424+
assertChipStackLockedByCurrentThread();
1425+
13731426
std::lock_guard lock(_lock);
13741427

13751428
_receivingReport = YES;
@@ -1807,11 +1860,14 @@ - (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eve
18071860

18081861
- (void)unitTestInjectAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport fromSubscription:(BOOL)isFromSubscription
18091862
{
1810-
dispatch_async(self.queue, ^{
1863+
[_deviceController asyncDispatchToMatterQueue:^{
18111864
[self _handleReportBegin];
1812-
[self _handleAttributeReport:attributeReport fromSubscription:isFromSubscription];
1813-
[self _handleReportEnd];
1814-
});
1865+
dispatch_async(self.queue, ^{
1866+
[self _handleAttributeReport:attributeReport fromSubscription:isFromSubscription];
1867+
[self _handleReportEnd];
1868+
});
1869+
}
1870+
errorHandler:nil];
18151871
}
18161872
#endif
18171873

@@ -2242,6 +2298,8 @@ - (void)unitTestResetSubscription
22422298
// assume lock is held
22432299
- (void)_setupSubscriptionWithReason:(NSString *)reason
22442300
{
2301+
assertChipStackLockedByCurrentThread();
2302+
22452303
os_unfair_lock_assert_owner(&self->_lock);
22462304

22472305
if (![self _subscriptionsAllowed]) {
@@ -2287,7 +2345,6 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
22872345
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription) * static_cast<int64_t>(NSEC_PER_SEC)), self.queue, ^{
22882346
mtr_strongify(self);
22892347
if (self != nil) {
2290-
std::lock_guard lock(self->_lock);
22912348
[self _markDeviceAsUnreachableIfNeverSubscribed];
22922349
}
22932350
});
@@ -2305,10 +2362,8 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
23052362
NSNumber * _Nullable retryDelay) {
23062363
if (error != nil) {
23072364
MTR_LOG_ERROR("%@ getSessionForNode error %@", self, error);
2308-
dispatch_async(self.queue, ^{
2309-
[self _handleSubscriptionError:error];
2310-
[self _handleSubscriptionReset:retryDelay];
2311-
});
2365+
[self _handleSubscriptionError:error];
2366+
[self _handleSubscriptionReset:retryDelay];
23122367
return;
23132368
}
23142369

@@ -2332,17 +2387,13 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
23322387
},
23332388
^(NSError * error) {
23342389
MTR_LOG_ERROR("%@ got subscription error %@", self, error);
2335-
dispatch_async(self.queue, ^{
2336-
// OnError
2337-
[self _handleSubscriptionError:error];
2338-
});
2390+
// OnError
2391+
[self _handleSubscriptionError:error];
23392392
},
23402393
^(NSError * error, NSNumber * resubscriptionDelayMs) {
23412394
MTR_LOG_ERROR("%@ got resubscription error %@ delay %@", self, error, resubscriptionDelayMs);
2342-
dispatch_async(self.queue, ^{
2343-
// OnResubscriptionNeeded
2344-
[self _handleResubscriptionNeededWithDelay:resubscriptionDelayMs];
2345-
});
2395+
// OnResubscriptionNeeded
2396+
[self _handleResubscriptionNeededWithDelay:resubscriptionDelayMs];
23462397
},
23472398
^(void) {
23482399
MTR_LOG("%@ got subscription established", self);
@@ -2373,23 +2424,17 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
23732424
self->_currentReadClient = nullptr;
23742425
self->_currentSubscriptionCallback = nullptr;
23752426

2376-
dispatch_async(self.queue, ^{
2377-
// OnDone
2378-
[self _handleSubscriptionReset:nil];
2379-
});
2427+
// OnDone
2428+
[self _doHandleSubscriptionReset:nil];
23802429
},
23812430
^(void) {
23822431
MTR_LOG("%@ got unsolicited message from publisher", self);
2383-
dispatch_async(self.queue, ^{
2384-
// OnUnsolicitedMessageFromPublisher
2385-
[self _handleUnsolicitedMessageFromPublisher];
2386-
});
2432+
// OnUnsolicitedMessageFromPublisher
2433+
[self _handleUnsolicitedMessageFromPublisher];
23872434
},
23882435
^(void) {
23892436
MTR_LOG("%@ got report begin", self);
2390-
dispatch_async(self.queue, ^{
2391-
[self _handleReportBegin];
2392-
});
2437+
[self _handleReportBegin];
23932438
},
23942439
^(void) {
23952440
MTR_LOG("%@ got report end", self);
@@ -2459,10 +2504,8 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
24592504
if (err != CHIP_NO_ERROR) {
24602505
NSError * error = [MTRError errorForCHIPErrorCode:err logContext:self];
24612506
MTR_LOG_ERROR("%@ SendAutoResubscribeRequest error %@", self, error);
2462-
dispatch_async(self.queue, ^{
2463-
[self _handleSubscriptionError:error];
2464-
[self _handleSubscriptionReset:nil];
2465-
});
2507+
[self _handleSubscriptionError:error];
2508+
[self _handleSubscriptionReset:nil];
24662509

24672510
return;
24682511
}

0 commit comments

Comments
 (0)