Skip to content

Commit 0afc132

Browse files
Fix leak in MTRBaseDevice tests. (project-chip#33275)
Our test code in deregisterReportHandlersWithQueue would directly delete the ReadClient, but that never called OnDone, and hence never deleted the BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> that we had also allocated. The fix is to put all the things we need to deallocate in the MTRReadClientContainer and consistently use it to clean those things up, including on all the various failure paths we have. Removes the CauseReadClientFailure function which did not in fact cause a failure, and instead just has the relevant test trigger a subscription drop.
1 parent beb7f11 commit 0afc132

File tree

3 files changed

+53
-100
lines changed

3 files changed

+53
-100
lines changed

src/darwin/Framework/CHIP/MTRBaseDevice.mm

+23-89
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,18 @@
9191
NSString * const MTREventIsHistoricalKey = @"eventIsHistorical";
9292

9393
class MTRDataValueDictionaryCallbackBridge;
94+
class MTRDataValueDictionaryDecodableType;
95+
template <typename DecodableValueType>
96+
class BufferedReadClientCallback;
9497

9598
@interface MTRReadClientContainer : NSObject
9699
@property (nonatomic, readwrite) app::ReadClient * readClientPtr;
100+
@property (nonatomic, readwrite) BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback;
97101
@property (nonatomic, readwrite) app::AttributePathParams * pathParams;
98102
@property (nonatomic, readwrite) app::EventPathParams * eventPathParams;
99103
@property (nonatomic, readwrite) uint64_t deviceID;
104+
105+
- (void)cleanup;
100106
- (void)onDone;
101107
@end
102108

@@ -157,22 +163,7 @@ static void PurgeReadClientContainers(
157163
[controller
158164
asyncDispatchToMatterQueue:^() {
159165
for (MTRReadClientContainer * container in listToDelete) {
160-
if (container.readClientPtr) {
161-
Platform::Delete(container.readClientPtr);
162-
container.readClientPtr = nullptr;
163-
}
164-
if (container.pathParams) {
165-
static_assert(std::is_trivially_destructible<AttributePathParams>::value,
166-
"AttributePathParams destructors won't get run");
167-
Platform::MemoryFree(container.pathParams);
168-
container.pathParams = nullptr;
169-
}
170-
if (container.eventPathParams) {
171-
static_assert(
172-
std::is_trivially_destructible<EventPathParams>::value, "EventPathParams destructors won't get run");
173-
Platform::MemoryFree(container.eventPathParams);
174-
container.eventPathParams = nullptr;
175-
}
166+
[container cleanup];
176167
}
177168
[listToDelete removeAllObjects];
178169
if (completion) {
@@ -204,47 +195,13 @@ static void PurgeCompletedReadClientContainers(uint64_t deviceId)
204195
[readClientContainersLock unlock];
205196
}
206197

207-
#ifdef DEBUG
208-
// This function is for unit testing only. This function closes all read clients.
209-
static void CauseReadClientFailure(
210-
MTRDeviceController * controller, uint64_t deviceId, dispatch_queue_t queue, void (^_Nullable completion)(void))
211-
{
212-
InitializeReadClientContainers();
213-
214-
NSMutableArray<MTRReadClientContainer *> * listToFail;
215-
NSNumber * key = [NSNumber numberWithUnsignedLongLong:deviceId];
216-
[readClientContainersLock lock];
217-
listToFail = readClientContainers[key];
218-
[readClientContainers removeObjectForKey:key];
219-
[readClientContainersLock unlock];
220-
221-
[controller
222-
asyncDispatchToMatterQueue:^() {
223-
for (MTRReadClientContainer * container in listToFail) {
224-
// Send auto resubscribe request again by read clients, which must fail.
225-
chip::app::ReadPrepareParams readParams;
226-
if (container.readClientPtr) {
227-
container.readClientPtr->SendAutoResubscribeRequest(std::move(readParams));
228-
}
229-
}
230-
if (completion) {
231-
dispatch_async(queue, completion);
232-
}
233-
}
234-
errorHandler:^(NSError * error) {
235-
// Can't fail things. Just put them back.
236-
ReinstateReadClientList(listToFail, key, queue, completion);
237-
}];
238-
}
239-
#endif
240-
241198
static bool CheckMemberOfType(NSDictionary<NSString *, id> * responseValue, NSString * memberName, Class expectedClass,
242199
NSString * errorMessage, NSError * __autoreleasing * error);
243200
static void LogStringAndReturnError(NSString * errorStr, CHIP_ERROR errorCode, NSError * __autoreleasing * error);
244201
static void LogStringAndReturnError(NSString * errorStr, MTRErrorCode errorCode, NSError * __autoreleasing * error);
245202

246203
@implementation MTRReadClientContainer
247-
- (void)onDone
204+
- (void)cleanup
248205
{
249206
if (_readClientPtr) {
250207
Platform::Delete(_readClientPtr);
@@ -260,26 +217,19 @@ - (void)onDone
260217
Platform::MemoryFree(_eventPathParams);
261218
_eventPathParams = nullptr;
262219
}
263-
PurgeCompletedReadClientContainers(_deviceID);
220+
if (_callback) {
221+
Platform::Delete(_callback);
222+
_callback = nullptr;
223+
}
264224
}
265225

266-
- (void)dealloc
226+
- (void)onDone
267227
{
268-
if (_readClientPtr) {
269-
Platform::Delete(_readClientPtr);
270-
_readClientPtr = nullptr;
271-
}
272-
if (_pathParams) {
273-
static_assert(std::is_trivially_destructible<AttributePathParams>::value, "AttributePathParams destructors won't get run");
274-
Platform::MemoryFree(_pathParams);
275-
_pathParams = nullptr;
276-
}
277-
if (_eventPathParams) {
278-
static_assert(std::is_trivially_destructible<EventPathParams>::value, "EventPathParams destructors won't get run");
279-
Platform::MemoryFree(_eventPathParams);
280-
_eventPathParams = nullptr;
281-
}
228+
[self cleanup];
229+
230+
PurgeCompletedReadClientContainers(_deviceID);
282231
}
232+
283233
@end
284234

285235
@implementation MTRBaseDevice
@@ -1730,6 +1680,7 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
17301680
dispatch_async(queue, ^{
17311681
reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_NO_MEMORY]);
17321682
});
1683+
[container cleanup];
17331684
return;
17341685
}
17351686
for (MTRAttributeRequestPath * attribute in attributes) {
@@ -1744,6 +1695,7 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
17441695
dispatch_async(queue, ^{
17451696
reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_NO_MEMORY]);
17461697
});
1698+
[container cleanup];
17471699
return;
17481700
}
17491701
for (MTREventRequestPath * event in events) {
@@ -1763,9 +1715,6 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
17631715

17641716
auto onDone = [container](BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback) {
17651717
[container onDone];
1766-
// Make sure we delete callback last, because doing that actually destroys our
1767-
// lambda, so we can't access captured values after that.
1768-
chip::Platform::Delete(callback);
17691718
};
17701719

17711720
auto callback = chip::Platform::MakeUnique<BufferedReadClientCallback<MTRDataValueDictionaryDecodableType>>(
@@ -1775,6 +1724,9 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
17751724
auto readClient = Platform::New<app::ReadClient>(
17761725
engine, exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Subscribe);
17771726

1727+
container.readClientPtr = readClient;
1728+
container.callback = callback.release();
1729+
17781730
if (!params.resubscribeAutomatically) {
17791731
err = readClient->SendRequest(readParams);
17801732
} else {
@@ -1785,23 +1737,12 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
17851737
dispatch_async(queue, ^{
17861738
reportHandler(nil, [MTRError errorForCHIPErrorCode:err]);
17871739
});
1788-
Platform::Delete(readClient);
1789-
if (container.pathParams != nullptr) {
1790-
Platform::MemoryFree(container.pathParams);
1791-
}
1792-
1793-
if (container.eventPathParams != nullptr) {
1794-
Platform::MemoryFree(container.eventPathParams);
1795-
}
1796-
container.pathParams = nullptr;
1797-
container.eventPathParams = nullptr;
1740+
[container cleanup];
17981741
return;
17991742
}
18001743

18011744
// Read clients will be purged when deregistered.
1802-
container.readClientPtr = readClient;
18031745
AddReadClientContainer(container.deviceID, container);
1804-
callback.release();
18051746
}];
18061747
}
18071748

@@ -2024,13 +1965,6 @@ - (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator
20241965
}
20251966

20261967
#ifdef DEBUG
2027-
// This method is for unit testing only
2028-
- (void)failSubscribers:(dispatch_queue_t)queue completion:(void (^)(void))completion
2029-
{
2030-
MTR_LOG_DEBUG("Causing failure in subscribers on purpose");
2031-
CauseReadClientFailure(self.deviceController, self.nodeID, queue, completion);
2032-
}
2033-
20341968
// The following method is for unit testing purpose only
20351969
+ (id)CHIPEncodeAndDecodeNSObject:(id)object
20361970
{

src/darwin/Framework/CHIPTests/MTRDeviceTests.m

+30-9
Original file line numberDiff line numberDiff line change
@@ -1082,8 +1082,8 @@ - (void)test012_SubscriptionError
10821082
XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
10831083
XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]);
10841084
if ([result[@"data"][@"value"] boolValue] == YES) {
1085-
[reportExpectation fulfill];
10861085
globalReportHandler = nil;
1086+
[reportExpectation fulfill];
10871087
}
10881088
};
10891089

@@ -1120,13 +1120,34 @@ - (void)test012_SubscriptionError
11201120
// Wait for report
11211121
[self waitForExpectations:[NSArray arrayWithObject:reportExpectation] timeout:kTimeoutInSeconds];
11221122

1123-
// Trigger reader failure
1124-
XCTestExpectation * failureExpectation = [self expectationWithDescription:@"failed on purpose"];
1125-
[device failSubscribers:queue
1126-
completion:^{
1127-
[failureExpectation fulfill];
1128-
}];
1129-
[self waitForExpectations:@[ failureExpectation ] timeout:kTimeoutInSeconds];
1123+
XCTestExpectation * errorExpectation1 = [self expectationWithDescription:@"First subscription errored out"];
1124+
XCTestExpectation * errorExpectation2 = [self expectationWithDescription:@"Second subscription errored out"];
1125+
1126+
globalReportHandler = ^(id _Nullable values, NSError * _Nullable error) {
1127+
XCTAssertNil(values);
1128+
XCTAssertNotNil(error);
1129+
globalReportHandler = nil;
1130+
[errorExpectation1 fulfill];
1131+
};
1132+
1133+
// Try to create a second subscription, which will cancel the first
1134+
// subscription. We can use a non-existent path here to cut down on the
1135+
// work that gets done.
1136+
params.replaceExistingSubscriptions = YES;
1137+
[device subscribeToAttributesWithEndpointID:@10000
1138+
clusterID:@6
1139+
attributeID:@0
1140+
params:params
1141+
queue:queue
1142+
reportHandler:^(id _Nullable values, NSError * _Nullable error) {
1143+
XCTAssertNil(values);
1144+
XCTAssertNotNil(error);
1145+
[errorExpectation2 fulfill];
1146+
}
1147+
subscriptionEstablished:^() {
1148+
XCTFail("Did not expect this subscription to succeed");
1149+
}];
1150+
[self waitForExpectations:@[ errorExpectation1, errorExpectation2 ] timeout:60];
11301151

11311152
deregisterExpectation = [self expectationWithDescription:@"Report handler deregistered"];
11321153
[device deregisterReportHandlersWithQueue:queue
@@ -1135,7 +1156,7 @@ - (void)test012_SubscriptionError
11351156
}];
11361157
[self waitForExpectations:@[ deregisterExpectation ] timeout:kTimeoutInSeconds];
11371158
}
1138-
#endif
1159+
#endif // DEBUG
11391160

11401161
- (void)test013_ReuseChipClusterObject
11411162
{

src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h

-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ NS_ASSUME_NONNULL_BEGIN
5454
@end
5555

5656
@interface MTRBaseDevice (TestDebug)
57-
- (void)failSubscribers:(dispatch_queue_t)queue completion:(void (^)(void))completion;
58-
5957
// Test function for whitebox testing
6058
+ (id)CHIPEncodeAndDecodeNSObject:(id)object;
6159
@end

0 commit comments

Comments
 (0)