Skip to content

Commit 3689694

Browse files
bzbarsky-applepull[bot]
authored andcommitted
Fix race when trying to use Darwin callback bridge from action block. (#23553)
We have some callback bridge action blocks that need access to the bridge itself. Since the action block was queued to run on a different thread from inside the bridge constructor, it could race with the bridge constructor finishing execution, leading to unexpected behavior (including possibly destruction of the bridge before its constructor finishes executing. Switch to running the action block after the bridge is constructed.
1 parent aa60828 commit 3689694

8 files changed

+32554
-42206
lines changed

src/darwin/Framework/CHIP/MTRBaseDevice.mm

+29-42
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,6 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVW
648648

649649
// Callback type to pass data value as an NSObject
650650
typedef void (*MTRDataValueDictionaryCallback)(void * context, id value);
651-
typedef void (*MTRErrorCallback)(void * context, CHIP_ERROR error);
652651

653652
// Rename to be generic for decode and encode
654653
class MTRDataValueDictionaryDecodableType {
@@ -689,9 +688,9 @@ CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const
689688
// Callback bridge for MTRDataValueDictionaryCallback
690689
class MTRDataValueDictionaryCallbackBridge : public MTRCallbackBridge<MTRDataValueDictionaryCallback> {
691690
public:
692-
MTRDataValueDictionaryCallbackBridge(dispatch_queue_t queue, MTRBaseDevice * device, MTRDeviceResponseHandler handler,
693-
MTRActionBlock action, bool keepAlive = false)
694-
: MTRCallbackBridge<MTRDataValueDictionaryCallback>(queue, device, handler, action, OnSuccessFn, keepAlive) {};
691+
MTRDataValueDictionaryCallbackBridge(
692+
dispatch_queue_t queue, MTRDeviceResponseHandler handler, MTRActionBlock action, bool keepAlive = false)
693+
: MTRCallbackBridge<MTRDataValueDictionaryCallback>(queue, handler, action, OnSuccessFn, keepAlive) {};
695694

696695
static void OnSuccessFn(void * context, id value) { DispatchSuccess(context, value); }
697696
};
@@ -791,14 +790,9 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID
791790
clusterID = (clusterID == nil) ? nil : [clusterID copy];
792791
attributeID = (attributeID == nil) ? nil : [attributeID copy];
793792
params = (params == nil) ? nil : [params copy];
794-
new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
795-
^(ExchangeManager & exchangeManager, const SessionHandle & session, chip::Callback::Cancelable * success,
796-
chip::Callback::Cancelable * failure) {
797-
auto successFn = chip::Callback::Callback<MTRDataValueDictionaryCallback>::FromCancelable(success);
798-
auto failureFn = chip::Callback::Callback<MTRErrorCallback>::FromCancelable(failure);
799-
auto context = successFn->mContext;
800-
auto successCb = successFn->mCall;
801-
auto failureCb = failureFn->mCall;
793+
auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion,
794+
^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb,
795+
MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
802796
auto resultArray = [[NSMutableArray alloc] init];
803797
auto resultSuccess = [[NSMutableArray alloc] init];
804798
auto resultFailure = [[NSMutableArray alloc] init];
@@ -842,23 +836,23 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
842836
readParams.mAttributePathParamsListSize = 1;
843837
readParams.mIsFabricFiltered = params.filterByFabric;
844838

845-
auto onDone = [resultArray, resultSuccess, resultFailure, context, successCb, failureCb](
839+
auto onDone = [resultArray, resultSuccess, resultFailure, bridge, successCb, failureCb](
846840
BufferedReadAttributeCallback<MTRDataValueDictionaryDecodableType> * callback) {
847841
if ([resultFailure count] > 0 || [resultSuccess count] == 0) {
848842
// Failure
849843
if (failureCb) {
850844
if ([resultFailure count] > 0) {
851-
failureCb(context, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
845+
failureCb(bridge, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
852846
} else if ([resultArray count] > 0) {
853-
failureCb(context, [MTRError errorToCHIPErrorCode:resultArray[0][MTRErrorKey]]);
847+
failureCb(bridge, [MTRError errorToCHIPErrorCode:resultArray[0][MTRErrorKey]]);
854848
} else {
855-
failureCb(context, CHIP_ERROR_READ_FAILED);
849+
failureCb(bridge, CHIP_ERROR_READ_FAILED);
856850
}
857851
}
858852
} else {
859853
// Success
860854
if (successCb) {
861-
successCb(context, resultArray);
855+
successCb(bridge, resultArray);
862856
}
863857
}
864858
chip::Platform::Delete(callback);
@@ -887,6 +881,7 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
887881
callback.release();
888882
return err;
889883
});
884+
std::move(*bridge).DispatchAction(self);
890885
}
891886

892887
- (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
@@ -897,14 +892,9 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
897892
queue:(dispatch_queue_t)queue
898893
completion:(MTRDeviceResponseHandler)completion
899894
{
900-
new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
901-
^(ExchangeManager & exchangeManager, const SessionHandle & session, chip::Callback::Cancelable * success,
902-
chip::Callback::Cancelable * failure) {
903-
auto successFn = chip::Callback::Callback<MTRDataValueDictionaryCallback>::FromCancelable(success);
904-
auto failureFn = chip::Callback::Callback<MTRErrorCallback>::FromCancelable(failure);
905-
auto context = successFn->mContext;
906-
auto successCb = successFn->mCall;
907-
auto failureCb = failureFn->mCall;
895+
auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion,
896+
^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb,
897+
MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
908898
auto resultArray = [[NSMutableArray alloc] init];
909899
auto resultSuccess = [[NSMutableArray alloc] init];
910900
auto resultFailure = [[NSMutableArray alloc] init];
@@ -929,22 +919,22 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
929919
};
930920

931921
auto onDoneCb
932-
= [context, successCb, failureCb, resultArray, resultSuccess, resultFailure](app::WriteClient * pWriteClient) {
922+
= [bridge, successCb, failureCb, resultArray, resultSuccess, resultFailure](app::WriteClient * pWriteClient) {
933923
if ([resultFailure count] > 0 || [resultSuccess count] == 0) {
934924
// Failure
935925
if (failureCb) {
936926
if ([resultFailure count] > 0) {
937-
failureCb(context, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
927+
failureCb(bridge, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
938928
} else if ([resultArray count] > 0) {
939-
failureCb(context, [MTRError errorToCHIPErrorCode:resultArray[0][MTRErrorKey]]);
929+
failureCb(bridge, [MTRError errorToCHIPErrorCode:resultArray[0][MTRErrorKey]]);
940930
} else {
941-
failureCb(context, CHIP_ERROR_WRITE_FAILED);
931+
failureCb(bridge, CHIP_ERROR_WRITE_FAILED);
942932
}
943933
}
944934
} else {
945935
// Success
946936
if (successCb) {
947-
successCb(context, resultArray);
937+
successCb(bridge, resultArray);
948938
}
949939
}
950940
};
@@ -956,6 +946,7 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
956946
onSuccessCb, onFailureCb, (timeoutMs == nil) ? NullOptional : Optional<uint16_t>([timeoutMs unsignedShortValue]),
957947
onDoneCb, NullOptional);
958948
});
949+
std::move(*bridge).DispatchAction(self);
959950
}
960951

961952
class NSObjectCommandCallback final : public app::CommandSender::Callback {
@@ -1045,14 +1036,9 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
10451036
commandFields = (commandFields == nil) ? nil : [commandFields copy];
10461037
timeoutMs = (timeoutMs == nil) ? nil : [timeoutMs copy];
10471038

1048-
new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
1049-
^(ExchangeManager & exchangeManager, const SessionHandle & session, chip::Callback::Cancelable * success,
1050-
chip::Callback::Cancelable * failure) {
1051-
auto successFn = chip::Callback::Callback<MTRDataValueDictionaryCallback>::FromCancelable(success);
1052-
auto failureFn = chip::Callback::Callback<MTRErrorCallback>::FromCancelable(failure);
1053-
auto context = successFn->mContext;
1054-
auto successCb = successFn->mCall;
1055-
auto failureCb = failureFn->mCall;
1039+
auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion,
1040+
^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb,
1041+
MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
10561042
auto resultArray = [[NSMutableArray alloc] init];
10571043
auto resultSuccess = [[NSMutableArray alloc] init];
10581044
auto resultFailure = [[NSMutableArray alloc] init];
@@ -1086,21 +1072,21 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
10861072
VerifyOrReturnError(decoder != nullptr, CHIP_ERROR_NO_MEMORY);
10871073

10881074
auto rawDecoderPtr = decoder.get();
1089-
auto onDoneCb = [rawDecoderPtr, context, successCb, failureCb, resultArray, resultSuccess, resultFailure](
1075+
auto onDoneCb = [rawDecoderPtr, bridge, successCb, failureCb, resultArray, resultSuccess, resultFailure](
10901076
app::CommandSender * commandSender) {
10911077
if ([resultFailure count] > 0 || [resultSuccess count] == 0) {
10921078
// Failure
10931079
if (failureCb) {
10941080
if ([resultFailure count] > 0) {
1095-
failureCb(context, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
1081+
failureCb(bridge, [MTRError errorToCHIPErrorCode:resultFailure[0]]);
10961082
} else {
1097-
failureCb(context, CHIP_ERROR_WRITE_FAILED);
1083+
failureCb(bridge, CHIP_ERROR_WRITE_FAILED);
10981084
}
10991085
}
11001086
} else {
11011087
// Success
11021088
if (successCb) {
1103-
successCb(context, resultArray);
1089+
successCb(bridge, resultArray);
11041090
}
11051091
}
11061092
chip::Platform::Delete(commandSender);
@@ -1121,6 +1107,7 @@ new MTRDataValueDictionaryCallbackBridge(queue, self, completion,
11211107
commandSender.release();
11221108
return CHIP_NO_ERROR;
11231109
});
1110+
std::move(*bridge).DispatchAction(self);
11241111
}
11251112

11261113
- (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID

src/darwin/Framework/CHIP/MTRCallbackBridgeBase_internal.h

+86-54
Original file line numberDiff line numberDiff line change
@@ -32,90 +32,122 @@
3232
* communication with the device in question has been established, as far as we
3333
* know.
3434
*/
35+
class MTRCallbackBridgeBase {
36+
};
3537

3638
// TODO: ADD NS_ASSUME_NONNULL_BEGIN to this header. When that happens, note
3739
// that in MTRActionBlock the two callback pointers are nonnull and the two
38-
// arguments of ResponseHandler are both nullable.
40+
// arguments of MTRResponseHandler are both nullable.
3941

40-
typedef void (^ResponseHandler)(id value, NSError * error);
41-
typedef CHIP_ERROR (^MTRActionBlock)(chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
42-
chip::Callback::Cancelable * success, chip::Callback::Cancelable * failure);
43-
typedef CHIP_ERROR (^MTRLocalActionBlock)(chip::Callback::Cancelable * success, chip::Callback::Cancelable * failure);
44-
typedef void (*DefaultFailureCallbackType)(void *, CHIP_ERROR);
42+
typedef void (^MTRResponseHandler)(id value, NSError * error);
43+
typedef void (*MTRErrorCallback)(void * context, CHIP_ERROR error);
4544

46-
template <class T> class MTRCallbackBridge {
45+
/**
46+
* The bridge will pass itself as the last argument to the action block.
47+
*
48+
* The action block must do one of three things:
49+
*
50+
* 1) Return an error.
51+
* 2) Call the "successCb" callback, with the bridge passed to the block as the
52+
* context, possibly asynchronously.
53+
* 3) Call the "failureCb" callback, with the bridge passed to the block as the
54+
* context, possibly asynchronously.
55+
*
56+
* For an MTRCallbackBridge that has keepAlive set to true, the success/failure
57+
* callbacks may be called multiple times. If keepAlive is false, there must be
58+
* no calls after the first one.
59+
*/
60+
template <typename SuccessCallback>
61+
using MTRActionBlockT = CHIP_ERROR (^)(chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
62+
SuccessCallback successCb, MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge);
63+
template <typename SuccessCallback>
64+
using MTRLocalActionBlockT = CHIP_ERROR (^)(SuccessCallback successCb, MTRErrorCallback failureCb);
65+
66+
template <class T> class MTRCallbackBridge : public MTRCallbackBridgeBase {
4767
public:
68+
using MTRActionBlock = MTRActionBlockT<T>;
69+
using MTRLocalActionBlock = MTRLocalActionBlockT<T>;
70+
4871
/**
49-
* Run the given MTRLocalActionBlock on the Matter thread, then handle
50-
* converting the value produced by the success callback to the right type
51-
* so it can be passed to a callback of the type we're templated over.
52-
*
53-
* Does not attempt to establish any sessions to devices. Must not be used
54-
* with any action blocks that need a session.
72+
* Construct a callback bridge, which can then have DispatcLocalAction() called
73+
* on it.
5574
*/
56-
MTRCallbackBridge(dispatch_queue_t queue, ResponseHandler handler, MTRLocalActionBlock action, T OnSuccessFn, bool keepAlive)
75+
MTRCallbackBridge(dispatch_queue_t queue, MTRResponseHandler handler, T OnSuccessFn, bool keepAlive)
5776
: mQueue(queue)
5877
, mHandler(handler)
5978
, mKeepAlive(keepAlive)
60-
, mSuccess(OnSuccessFn, this)
61-
, mFailure(OnFailureFn, this)
79+
, mSuccess(OnSuccessFn)
80+
, mFailure(OnFailureFn)
6281
{
63-
LogRequestStart();
64-
65-
// For now keep sync dispatch here.
66-
dispatch_sync(chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{
67-
CHIP_ERROR err = action(mSuccess.Cancel(), mFailure.Cancel());
68-
if (err != CHIP_NO_ERROR) {
69-
NSLog(@"Failure performing action. C++-mangled success callback type: '%s', error: %s", typeid(T).name(),
70-
chip::ErrorStr(err));
71-
72-
// Take the normal async error-reporting codepath. This will also
73-
// handle cleaning us up properly.
74-
OnFailureFn(this, err);
75-
}
76-
});
7782
}
7883

7984
/**
80-
* Run the given MTRActionBlock on the Matter thread, after getting a CASE
81-
* session (possibly pre-existing) to the given node ID on the fabric
82-
* represented by the given MTRDeviceController. On success, convert the
83-
* success value to whatever type it needs to be to call the callback type
84-
* we're templated over.
85+
* Construct a callback bridge, which can then have DispatchAction() called
86+
* on it.
8587
*/
86-
MTRCallbackBridge(dispatch_queue_t queue, chip::NodeId nodeID, MTRDeviceController * controller, ResponseHandler handler,
87-
MTRActionBlock action, T OnSuccessFn, bool keepAlive)
88+
MTRCallbackBridge(dispatch_queue_t queue, MTRResponseHandler handler, MTRActionBlock action, T OnSuccessFn, bool keepAlive)
8889
: mQueue(queue)
8990
, mHandler(handler)
9091
, mAction(action)
9192
, mKeepAlive(keepAlive)
92-
, mSuccess(OnSuccessFn, this)
93-
, mFailure(OnFailureFn, this)
93+
, mSuccess(OnSuccessFn)
94+
, mFailure(OnFailureFn)
9495
{
95-
ActionWithNodeID(nodeID, controller);
9696
}
9797

98+
/**
99+
* Run the given MTRActionBlock on the Matter thread, after getting a CASE
100+
* session (possibly pre-existing) to the given node ID on the fabric
101+
* represented by the given MTRDeviceController. On success, convert the
102+
* success value to whatever type it needs to be to call the callback type
103+
* we're templated over. Once this function has been called, on a callback
104+
* bridge allocated with `new`, the bridge object must not be accessed by
105+
* the caller. The action block will handle deleting the bridge.
106+
*/
107+
void DispatchAction(chip::NodeId nodeID, MTRDeviceController * controller) && { ActionWithNodeID(nodeID, controller); }
108+
98109
/**
99110
* Run the given MTRActionBlock on the Matter thread after getting a secure
100111
* session corresponding to the given MTRBaseDevice. On success, convert
101112
* the success value to whatever type it needs to be to call the callback
102-
* type we're templated over.
113+
* type we're templated over. Once this function has been called, on a callback
114+
* bridge allocated with `new`, the bridge object must not be accessed by
115+
* the caller. The action block will handle deleting the bridge.
103116
*/
104-
MTRCallbackBridge(dispatch_queue_t queue, MTRBaseDevice * device, ResponseHandler handler, MTRActionBlock action, T OnSuccessFn,
105-
bool keepAlive)
106-
: mQueue(queue)
107-
, mHandler(handler)
108-
, mAction(action)
109-
, mKeepAlive(keepAlive)
110-
, mSuccess(OnSuccessFn, this)
111-
, mFailure(OnFailureFn, this)
117+
void DispatchAction(MTRBaseDevice * device) &&
112118
{
113119
if (device.isPASEDevice) {
114120
ActionWithPASEDevice(device);
115121
} else {
116122
ActionWithNodeID(device.nodeID, device.deviceController);
117123
}
118-
};
124+
}
125+
126+
/**
127+
* Run the given MTRLocalActionBlock on the Matter thread, then handle
128+
* converting the value produced by the success callback to the right type
129+
* so it can be passed to a callback of the type we're templated over.
130+
*
131+
* Does not attempt to establish any sessions to devices. Must not be used
132+
* with any action blocks that need a session.
133+
*/
134+
void DispatchLocalAction(MTRLocalActionBlock action)
135+
{
136+
LogRequestStart();
137+
138+
// For now keep sync dispatch here.
139+
dispatch_sync(chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{
140+
CHIP_ERROR err = action(mSuccess, mFailure);
141+
if (err != CHIP_NO_ERROR) {
142+
NSLog(@"Failure performing action. C++-mangled success callback type: '%s', error: %s", typeid(T).name(),
143+
chip::ErrorStr(err));
144+
145+
// Take the normal async error-reporting codepath. This will also
146+
// handle cleaning us up properly.
147+
OnFailureFn(this, err);
148+
}
149+
});
150+
}
119151

120152
void ActionWithPASEDevice(MTRBaseDevice * device)
121153
{
@@ -167,7 +199,7 @@ template <class T> class MTRCallbackBridge {
167199
return;
168200
}
169201

170-
CHIP_ERROR err = action(*exchangeManager, session.Value(), mSuccess.Cancel(), mFailure.Cancel());
202+
CHIP_ERROR err = action(*exchangeManager, session.Value(), mSuccess, mFailure, this);
171203
if (err != CHIP_NO_ERROR) {
172204
NSLog(@"Failure performing action. C++-mangled success callback type: '%s', error: %s", typeid(T).name(),
173205
chip::ErrorStr(err));
@@ -218,12 +250,12 @@ template <class T> class MTRCallbackBridge {
218250
});
219251
}
220252

221-
ResponseHandler mHandler;
253+
MTRResponseHandler mHandler;
222254
MTRActionBlock mAction;
223255
bool mKeepAlive;
224256

225-
chip::Callback::Callback<T> mSuccess;
226-
chip::Callback::Callback<DefaultFailureCallbackType> mFailure;
257+
T mSuccess;
258+
MTRErrorCallback mFailure;
227259

228260
// Measure the time it took for the callback to trigger
229261
NSDate * mRequestTime;

0 commit comments

Comments
 (0)