Skip to content

Commit 9d04e84

Browse files
bzbarsky-appleselissia
authored andcommitted
Better fix for crashes around MTRBaseSubscriptionCallback. (project-chip#23076)
project-chip#22978 accidentally reintroduced the crash that project-chip#22324 had fixed. To avoid more issues along these lines: 1) Add unit tests that reproduce the crashes described in project-chip#22320 (with the changes from project-chip#22978) and project-chip#22935 (without those changes). 2) Change MTRBaseSubscriptionCallback to always invoke its callbacks synchronously, on the Matter queue, so that we can clean up the MTRClusterStateCacheContainer's pointer to the ClusterStateCache before it gets deleted on the Matter queue. 3) Move the queueing of callbacks to the client queue into the consumers of MTRBaseSubscriptionCallback, so they can do whatever sync work they need (like the above cleanup) before going async. 4) Update documentation.
1 parent 71a6b97 commit 9d04e84

File tree

5 files changed

+309
-132
lines changed

5 files changed

+309
-132
lines changed

src/darwin/Framework/CHIP/MTRBaseDevice.mm

+117-77
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ - (void)invalidateCASESession
251251

252252
class SubscriptionCallback final : public MTRBaseSubscriptionCallback {
253253
public:
254-
SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
254+
SubscriptionCallback(DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
255255
ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionScheduledHandler,
256256
MTRSubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler)
257-
: MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback,
258-
resubscriptionScheduledHandler, subscriptionEstablishedHandler, onDoneHandler)
257+
: MTRBaseSubscriptionCallback(attributeReportCallback, eventReportCallback, errorCallback, resubscriptionScheduledHandler,
258+
subscriptionEstablishedHandler, onDoneHandler)
259259
{
260260
}
261261

@@ -286,80 +286,120 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
286286
// Copy params before going async.
287287
params = [params copy];
288288

289-
[self.deviceController
290-
getSessionForNode:self.nodeID
291-
completion:^(ExchangeManager * _Nullable exchangeManager, const Optional<SessionHandle> & session,
292-
NSError * _Nullable error) {
293-
if (error != nil) {
294-
dispatch_async(queue, ^{
295-
errorHandler(error);
296-
});
297-
return;
298-
}
299-
300-
// Wildcard endpoint, cluster, attribute, event.
301-
auto attributePath = std::make_unique<AttributePathParams>();
302-
auto eventPath = std::make_unique<EventPathParams>();
303-
ReadPrepareParams readParams(session.Value());
304-
readParams.mMinIntervalFloorSeconds = [params.minInterval unsignedShortValue];
305-
readParams.mMaxIntervalCeilingSeconds = [params.maxInterval unsignedShortValue];
306-
readParams.mpAttributePathParamsList = attributePath.get();
307-
readParams.mAttributePathParamsListSize = 1;
308-
readParams.mpEventPathParamsList = eventPath.get();
309-
readParams.mEventPathParamsListSize = 1;
310-
readParams.mIsFabricFiltered = params.fabricFiltered;
311-
readParams.mKeepSubscriptions = params.keepPreviousSubscriptions;
312-
313-
std::unique_ptr<SubscriptionCallback> callback;
314-
std::unique_ptr<ReadClient> readClient;
315-
std::unique_ptr<ClusterStateCache> clusterStateCache;
316-
if (clusterStateCacheContainer) {
317-
__weak MTRClusterStateCacheContainer * weakPtr = clusterStateCacheContainer;
318-
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler, eventReportHandler,
319-
errorHandler, resubscriptionScheduled, subscriptionEstablished, ^{
320-
MTRClusterStateCacheContainer * container = weakPtr;
321-
if (container) {
322-
container.cppClusterStateCache = nullptr;
323-
}
324-
});
325-
clusterStateCache = std::make_unique<ClusterStateCache>(*callback.get());
326-
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
327-
clusterStateCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
328-
} else {
329-
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler, eventReportHandler,
330-
errorHandler, resubscriptionScheduled, subscriptionEstablished, nil);
331-
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
332-
callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
333-
}
334-
335-
CHIP_ERROR err;
336-
if (!params.autoResubscribe) {
337-
err = readClient->SendRequest(readParams);
338-
} else {
339-
// SendAutoResubscribeRequest cleans up the params, even on failure.
340-
attributePath.release();
341-
eventPath.release();
342-
err = readClient->SendAutoResubscribeRequest(std::move(readParams));
343-
}
344-
345-
if (err != CHIP_NO_ERROR) {
346-
dispatch_async(queue, ^{
347-
errorHandler([MTRError errorForCHIPErrorCode:err]);
348-
});
349-
350-
return;
351-
}
352-
353-
if (clusterStateCacheContainer) {
354-
clusterStateCacheContainer.cppClusterStateCache = clusterStateCache.get();
355-
// ClusterStateCache will be deleted when OnDone is called or an error is encountered as well.
356-
callback->AdoptClusterStateCache(std::move(clusterStateCache));
357-
}
358-
// Callback and ReadClient will be deleted when OnDone is called or an error is
359-
// encountered.
360-
callback->AdoptReadClient(std::move(readClient));
361-
callback.release();
362-
}];
289+
[self.deviceController getSessionForNode:self.nodeID
290+
completion:^(ExchangeManager * _Nullable exchangeManager, const Optional<SessionHandle> & session,
291+
NSError * _Nullable error) {
292+
if (error != nil) {
293+
dispatch_async(queue, ^{
294+
errorHandler(error);
295+
});
296+
return;
297+
}
298+
299+
// Wildcard endpoint, cluster, attribute, event.
300+
auto attributePath = std::make_unique<AttributePathParams>();
301+
auto eventPath = std::make_unique<EventPathParams>();
302+
ReadPrepareParams readParams(session.Value());
303+
readParams.mMinIntervalFloorSeconds = [params.minInterval unsignedShortValue];
304+
readParams.mMaxIntervalCeilingSeconds = [params.maxInterval unsignedShortValue];
305+
readParams.mpAttributePathParamsList = attributePath.get();
306+
readParams.mAttributePathParamsListSize = 1;
307+
readParams.mpEventPathParamsList = eventPath.get();
308+
readParams.mEventPathParamsListSize = 1;
309+
readParams.mIsFabricFiltered = params.fabricFiltered;
310+
readParams.mKeepSubscriptions = params.keepPreviousSubscriptions;
311+
312+
std::unique_ptr<ClusterStateCache> clusterStateCache;
313+
ReadClient::Callback * callbackForReadClient = nullptr;
314+
OnDoneHandler onDoneHandler = nil;
315+
316+
if (clusterStateCacheContainer) {
317+
__weak MTRClusterStateCacheContainer * weakPtr = clusterStateCacheContainer;
318+
onDoneHandler = ^{
319+
// This, like all manipulation of cppClusterStateCache, needs to run on the Matter
320+
// queue.
321+
MTRClusterStateCacheContainer * container = weakPtr;
322+
if (container) {
323+
container.cppClusterStateCache = nullptr;
324+
}
325+
};
326+
}
327+
328+
auto callback = std::make_unique<SubscriptionCallback>(
329+
^(NSArray * value) {
330+
dispatch_async(queue, ^{
331+
if (attributeReportHandler != nil) {
332+
attributeReportHandler(value);
333+
}
334+
});
335+
},
336+
^(NSArray * value) {
337+
dispatch_async(queue, ^{
338+
if (eventReportHandler != nil) {
339+
eventReportHandler(value);
340+
}
341+
});
342+
},
343+
^(NSError * error) {
344+
dispatch_async(queue, ^{
345+
errorHandler(error);
346+
});
347+
},
348+
^(NSError * error, NSNumber * resubscriptionDelay) {
349+
dispatch_async(queue, ^{
350+
if (resubscriptionScheduled != nil) {
351+
resubscriptionScheduled(error, resubscriptionDelay);
352+
}
353+
});
354+
},
355+
^(void) {
356+
dispatch_async(queue, ^{
357+
if (subscriptionEstablished != nil) {
358+
subscriptionEstablished();
359+
}
360+
});
361+
},
362+
onDoneHandler);
363+
364+
if (clusterStateCacheContainer) {
365+
clusterStateCache = std::make_unique<ClusterStateCache>(*callback.get());
366+
callbackForReadClient = &clusterStateCache->GetBufferedCallback();
367+
} else {
368+
callbackForReadClient = &callback->GetBufferedCallback();
369+
}
370+
371+
auto readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(),
372+
exchangeManager, *callbackForReadClient, ReadClient::InteractionType::Subscribe);
373+
374+
CHIP_ERROR err;
375+
if (!params.autoResubscribe) {
376+
err = readClient->SendRequest(readParams);
377+
} else {
378+
// SendAutoResubscribeRequest cleans up the params, even on failure.
379+
attributePath.release();
380+
eventPath.release();
381+
err = readClient->SendAutoResubscribeRequest(std::move(readParams));
382+
}
383+
384+
if (err != CHIP_NO_ERROR) {
385+
dispatch_async(queue, ^{
386+
errorHandler([MTRError errorForCHIPErrorCode:err]);
387+
});
388+
389+
return;
390+
}
391+
392+
if (clusterStateCacheContainer) {
393+
clusterStateCacheContainer.cppClusterStateCache = clusterStateCache.get();
394+
// ClusterStateCache will be deleted when OnDone is called or an error is encountered as
395+
// well.
396+
callback->AdoptClusterStateCache(std::move(clusterStateCache));
397+
}
398+
// Callback and ReadClient will be deleted when OnDone is called or an error is
399+
// encountered.
400+
callback->AdoptReadClient(std::move(readClient));
401+
callback.release();
402+
}];
363403
}
364404

365405
// Convert TLV data into data-value dictionary as described in MTRDeviceResponseHandler

src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h

+19-11
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,16 @@
3535
/**
3636
* This file defines a base class for subscription callbacks used by
3737
* MTRBaseDevice and MTRDevice. This base class handles everything except the
38-
* actual conversion from the incoming data to the desired data.
38+
* actual conversion from the incoming data to the desired data and the dispatch
39+
* of callbacks to the relevant client queues. Its callbacks are called on the
40+
* Matter queue. This allows MTRDevice and MTRBaseDevice to do any necessary
41+
* sync cleanup work before dispatching to the client callbacks on the client
42+
* queue.
43+
*
44+
* After onDoneHandler is invoked, this object will at some point delete itself
45+
* and destroy anything it owns (such as the ReadClient or the
46+
* ClusterStateCache). Consumers should drop references to all the relevant
47+
* objects in that handler. This deletion will happen on the Matter queue.
3948
*
4049
* The desired data is assumed to be NSObjects that can be stored in NSArray.
4150
*/
@@ -49,12 +58,10 @@ typedef void (^OnDoneHandler)(void);
4958

5059
class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callback {
5160
public:
52-
MTRBaseSubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback,
53-
DataReportCallback eventReportCallback, ErrorCallback errorCallback,
54-
MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionCallback,
61+
MTRBaseSubscriptionCallback(DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
62+
ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionCallback,
5563
SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler)
56-
: mQueue(queue)
57-
, mAttributeReportCallback(attributeReportCallback)
64+
: mAttributeReportCallback(attributeReportCallback)
5865
, mEventReportCallback(eventReportCallback)
5966
, mErrorCallback(errorCallback)
6067
, mResubscriptionCallback(resubscriptionCallback)
@@ -117,10 +124,9 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac
117124
NSMutableArray * _Nullable mEventReports = nil;
118125

119126
private:
120-
dispatch_queue_t mQueue;
121127
DataReportCallback _Nullable mAttributeReportCallback = nil;
122128
DataReportCallback _Nullable mEventReportCallback = nil;
123-
// We set mErrorCallback to nil when queueing error reports, so we
129+
// We set mErrorCallback to nil before calling the error callback, so we
124130
// make sure to only report one error.
125131
ErrorCallback _Nullable mErrorCallback = nil;
126132
MTRDeviceResubscriptionScheduledHandler _Nullable mResubscriptionCallback = nil;
@@ -138,9 +144,11 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac
138144
// To handle this, enforce the following rules:
139145
//
140146
// 1) We guarantee that mErrorCallback is only invoked with an error once.
141-
// 2) We ensure that we delete ourselves and the passed in ReadClient only from OnDone or a queued-up
142-
// error callback, but not both, by tracking whether we have a queued-up
143-
// deletion.
147+
// 2) We guarantee that mOnDoneHandler is only invoked once, and always
148+
// invoked before we delete ourselves.
149+
// 3) We ensure that we delete ourselves and the passed in ReadClient only
150+
// from OnDone or from an error callback but not both, by tracking whether
151+
// we have a queued-up deletion.
144152
std::unique_ptr<chip::app::ReadClient> mReadClient;
145153
std::unique_ptr<chip::app::ClusterStateCache> mClusterStateCache;
146154
bool mHaveQueuedDeletion = false;

src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm

+15-29
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,17 @@
3333
{
3434
__block NSArray * attributeReports = mAttributeReports;
3535
mAttributeReports = nil;
36-
__block auto attributeCallback = mAttributeReportCallback;
36+
auto attributeCallback = mAttributeReportCallback;
3737

3838
__block NSArray * eventReports = mEventReports;
3939
mEventReports = nil;
40-
__block auto eventCallback = mEventReportCallback;
40+
auto eventCallback = mEventReportCallback;
4141

4242
if (attributeCallback != nil && attributeReports.count) {
43-
dispatch_async(mQueue, ^{
44-
attributeCallback(attributeReports);
45-
});
43+
attributeCallback(attributeReports);
4644
}
4745
if (eventCallback != nil && eventReports.count) {
48-
dispatch_async(mQueue, ^{
49-
eventCallback(eventReports);
50-
});
46+
eventCallback(eventReports);
5147
}
5248
}
5349

@@ -96,7 +92,8 @@
9692
void MTRBaseSubscriptionCallback::OnSubscriptionEstablished(SubscriptionId aSubscriptionId)
9793
{
9894
if (mSubscriptionEstablishedHandler) {
99-
dispatch_async(mQueue, mSubscriptionEstablishedHandler);
95+
auto subscriptionEstablishedHandler = mSubscriptionEstablishedHandler;
96+
subscriptionEstablishedHandler();
10097
}
10198
}
10299

@@ -109,9 +106,7 @@
109106
auto callback = mResubscriptionCallback;
110107
auto error = [MTRError errorForCHIPErrorCode:aTerminationCause];
111108
auto delayMs = @(apReadClient->ComputeTimeTillNextSubscription());
112-
dispatch_async(mQueue, ^{
113-
callback(error, delayMs);
114-
});
109+
callback(error, delayMs);
115110
}
116111
return CHIP_NO_ERROR;
117112
}
@@ -129,30 +124,21 @@
129124
return;
130125
}
131126

132-
__block ErrorCallback callback = mErrorCallback;
133127
__block auto * myself = this;
128+
129+
auto errorCallback = mErrorCallback;
134130
mErrorCallback = nil;
135131
mAttributeReportCallback = nil;
136132
mEventReportCallback = nil;
137-
__auto_type onDoneHandler = mOnDoneHandler;
133+
auto onDoneHandler = mOnDoneHandler;
138134
mOnDoneHandler = nil;
139-
dispatch_async(mQueue, ^{
140-
callback(err);
141-
});
142135

136+
errorCallback(err);
143137
if (onDoneHandler) {
144-
// To guarantee the async onDoneHandler call is made before
145-
// deletion, so that clean up can happen while the callback
146-
// object is still alive (and therefore cluster cache), queue
147-
// deletion after calling the onDoneHandler
148-
mHaveQueuedDeletion = true;
149-
dispatch_async(mQueue, ^{
150-
onDoneHandler();
151-
dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{
152-
delete myself;
153-
});
154-
});
155-
} else if (aCancelSubscription) {
138+
onDoneHandler();
139+
}
140+
141+
if (aCancelSubscription) {
156142
// We can't synchronously delete ourselves, because we're inside one of
157143
// the ReadClient callbacks and we need to outlive the callback's
158144
// execution. Queue an async deletion on the Matter queue (where we are

0 commit comments

Comments
 (0)