Skip to content

Commit 17c8285

Browse files
bzbarsky-applewoody-applejtung-apple
authored
Use NSCache for the MTRDevice attribute cache when possible. (#33125)
* Use NSCache for the MTRDevice attribute cache when possible. When we are storing our cluster data persistently, we don't have to ensure it's pinned in memory all the time. Use NSCache, and reload from storage as needed. * Update src/darwin/Framework/CHIP/MTRDevice.mm Co-authored-by: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> * Update src/darwin/Framework/CHIP/MTRDevice.mm Co-authored-by: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> --------- Co-authored-by: Justin Wood <woody@apple.com> Co-authored-by: Jeff Tung <100387939+jtung-apple@users.noreply.github.com>
1 parent 88595f0 commit 17c8285

8 files changed

+243
-70
lines changed

src/darwin/Framework/CHIP/MTRDevice.mm

+136-50
Large diffs are not rendered by default.

src/darwin/Framework/CHIP/MTRDeviceController.mm

+2-2
Original file line numberDiff line numberDiff line change
@@ -945,14 +945,14 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N
945945

946946
if (prefetchedClusterData) {
947947
if (prefetchedClusterData.count) {
948-
[deviceToReturn setClusterData:prefetchedClusterData];
948+
[deviceToReturn setPersistedClusterData:prefetchedClusterData];
949949
}
950950
} else {
951951
// Load persisted cluster data if they exist.
952952
NSDictionary * clusterData = [_controllerDataStore getStoredClusterDataForNodeID:nodeID];
953953
MTR_LOG_INFO("Loaded %lu cluster data from storage for %@", static_cast<unsigned long>(clusterData.count), deviceToReturn);
954954
if (clusterData.count) {
955-
[deviceToReturn setClusterData:clusterData];
955+
[deviceToReturn setPersistedClusterData:clusterData];
956956
}
957957
}
958958

src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ typedef void (^MTRDeviceControllerDataStoreClusterDataHandler)(NSDictionary<NSNu
7373
* Storage for MTRDevice attribute read cache. This is local-only storage as an optimization. New controller devices using MTRDevice API can prime their own local cache from devices directly.
7474
*/
7575
- (nullable NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)getStoredClusterDataForNodeID:(NSNumber *)nodeID;
76+
- (nullable MTRDeviceClusterData *)getStoredClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID;
7677
- (void)storeClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData forNodeID:(NSNumber *)nodeID;
7778
- (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID;
7879
- (void)clearAllStoredClusterData;

src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm

+12
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,18 @@ - (void)clearAllStoredClusterData
753753
return clusterDataToReturn;
754754
}
755755

756+
- (nullable MTRDeviceClusterData *)getStoredClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
757+
{
758+
// Don't bother checking our indices here, since this will only be called
759+
// when the consumer knows that we're supposed to have data for this cluster
760+
// path.
761+
__block MTRDeviceClusterData * clusterDataToReturn = nil;
762+
dispatch_sync(_storageDelegateQueue, ^{
763+
clusterDataToReturn = [self _fetchClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
764+
});
765+
return clusterDataToReturn;
766+
}
767+
756768
// Utility for constructing dictionary of nodeID to cluster data from dictionary of storage keys
757769
- (nullable NSDictionary<NSNumber *, NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *> *)_getClusterDataFromSecureLocalValues:(NSDictionary<NSString *, id> *)secureLocalValues
758770
{

src/darwin/Framework/CHIP/MTRDevice_Internal.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ MTR_TESTABLE
8282
@property (nonatomic) dispatch_queue_t queue;
8383
@property (nonatomic, readonly) MTRAsyncWorkQueue<MTRDevice *> * asyncWorkQueue;
8484

85-
// Method to insert cluster data
85+
// Method to insert persisted cluster data
8686
// Contains data version information and attribute values.
87-
- (void)setClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData;
87+
- (void)setPersistedClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData;
8888

8989
#ifdef DEBUG
9090
- (NSUInteger)unitTestAttributeCount;

src/darwin/Framework/CHIPTests/MTRDeviceTests.m

+22-9
Original file line numberDiff line numberDiff line change
@@ -1490,16 +1490,16 @@ - (void)test017_TestMTRDeviceBasics
14901490
XCTestExpectation * onTimeWriteSuccess = [self expectationWithDescription:@"OnTime write success"];
14911491
XCTestExpectation * onTimePreviousValue = [self expectationWithDescription:@"OnTime previous value"];
14921492
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * data) {
1493-
for (NSDictionary<NSString *, id> * attributeReponseValue in data) {
1494-
MTRAttributePath * path = attributeReponseValue[MTRAttributePathKey];
1493+
for (NSDictionary<NSString *, id> * attributeResponseValue in data) {
1494+
MTRAttributePath * path = attributeResponseValue[MTRAttributePathKey];
14951495
if (path.cluster.unsignedIntValue == MTRClusterIDTypeOnOffID && path.attribute.unsignedLongValue == MTRAttributeIDTypeClusterOnOffAttributeOnTimeID) {
1496-
NSDictionary * dataValue = attributeReponseValue[MTRDataKey];
1496+
NSDictionary * dataValue = attributeResponseValue[MTRDataKey];
14971497
NSNumber * onTimeValue = dataValue[MTRValueKey];
14981498
if (onTimeValue && (onTimeValue.unsignedIntValue == testOnTimeValue)) {
14991499
[onTimeWriteSuccess fulfill];
15001500
}
15011501

1502-
NSDictionary * previousDataValue = attributeReponseValue[MTRPreviousDataKey];
1502+
NSDictionary * previousDataValue = attributeResponseValue[MTRPreviousDataKey];
15031503
NSNumber * previousOnTimeValue = previousDataValue[MTRValueKey];
15041504
if (previousOnTimeValue) {
15051505
[onTimePreviousValue fulfill];
@@ -1517,15 +1517,28 @@ - (void)test017_TestMTRDeviceBasics
15171517

15181518
[self waitForExpectations:@[ onTimeWriteSuccess, onTimePreviousValue ] timeout:10];
15191519

1520+
__auto_type getOnOffValue = ^{
1521+
return [device readAttributeWithEndpointID:@(1)
1522+
clusterID:@(MTRClusterIDTypeOnOffID)
1523+
attributeID:@(MTRAttributeIDTypeClusterOnOffAttributeOnOffID)
1524+
params:nil];
1525+
};
1526+
__auto_type * onOffValue = getOnOffValue();
1527+
1528+
[device unitTestClearClusterData];
1529+
1530+
// Test that we can still get the value (will get paged in from storage).
1531+
XCTAssertEqualObjects(getOnOffValue(), onOffValue);
1532+
15201533
// Test if errors are properly received
15211534
// TODO: We might stop reporting these altogether from MTRDevice, and then
15221535
// this test will need updating.
15231536
__auto_type * readThroughForUnknownAttributesParams = [[MTRReadParams alloc] init];
15241537
readThroughForUnknownAttributesParams.assumeUnknownAttributesReportable = NO;
15251538
XCTestExpectation * attributeReportErrorExpectation = [self expectationWithDescription:@"Attribute read error"];
15261539
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * data) {
1527-
for (NSDictionary<NSString *, id> * attributeReponseValue in data) {
1528-
if (attributeReponseValue[MTRErrorKey]) {
1540+
for (NSDictionary<NSString *, id> * attributeResponseValue in data) {
1541+
if (attributeResponseValue[MTRErrorKey]) {
15291542
[attributeReportErrorExpectation fulfill];
15301543
}
15311544
}
@@ -2599,10 +2612,10 @@ - (void)test029_MTRDeviceWriteCoalescing
25992612
uint16_t testOnTimeValue = 10;
26002613
XCTestExpectation * onTimeWriteSuccess = [self expectationWithDescription:@"OnTime write success"];
26012614
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * data) {
2602-
for (NSDictionary<NSString *, id> * attributeReponseValue in data) {
2603-
MTRAttributePath * path = attributeReponseValue[MTRAttributePathKey];
2615+
for (NSDictionary<NSString *, id> * attributeResponseValue in data) {
2616+
MTRAttributePath * path = attributeResponseValue[MTRAttributePathKey];
26042617
if (path.cluster.unsignedIntValue == MTRClusterIDTypeOnOffID && path.attribute.unsignedLongValue == MTRAttributeIDTypeClusterOnOffAttributeOnTimeID) {
2605-
NSDictionary * dataValue = attributeReponseValue[MTRDataKey];
2618+
NSDictionary * dataValue = attributeResponseValue[MTRDataKey];
26062619
NSNumber * onTimeValue = dataValue[MTRValueKey];
26072620
if ([onTimeValue isEqual:@(testOnTimeValue + 4)]) {
26082621
[onTimeWriteSuccess fulfill];

src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift

+67-7
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ class MTRSwiftDeviceTests : XCTestCase {
196196
// can satisfy the test below.
197197
let gotReportsExpectation = expectation(description: "Attribute and Event reports have been received")
198198
var eventReportsReceived : Int = 0
199+
var reportEnded = false
200+
var gotOneNonPrimingEvent = false
201+
// Skipping the gotNonPrimingEventExpectation test (compare the ObjC test) for now,
202+
// because we can't do the debug-only unitTestInjectEventReport here.
199203
delegate.onEventDataReceived = { (eventReport: [[ String: Any ]]) -> Void in
200204
eventReportsReceived += eventReport.count
201205

@@ -210,9 +214,23 @@ class MTRSwiftDeviceTests : XCTestCase {
210214
} else if (eventTimeType == MTREventTimeType.timestampDate) {
211215
XCTAssertNotNil(eventDict[MTREventTimestampDateKey])
212216
}
217+
218+
if (!reportEnded) {
219+
let reportIsHistorical = eventDict[MTREventIsHistoricalKey] as! NSNumber?
220+
XCTAssertNotNil(reportIsHistorical);
221+
XCTAssertTrue(reportIsHistorical!.boolValue);
222+
} else {
223+
if (!gotOneNonPrimingEvent) {
224+
let reportIsHistorical = eventDict[MTREventIsHistoricalKey] as! NSNumber?
225+
XCTAssertNotNil(reportIsHistorical)
226+
XCTAssertFalse(reportIsHistorical!.boolValue)
227+
gotOneNonPrimingEvent = true
228+
}
229+
}
213230
}
214231
}
215232
delegate.onReportEnd = { () -> Void in
233+
reportEnded = true
216234
gotReportsExpectation.fulfill()
217235
}
218236

@@ -280,21 +298,59 @@ class MTRSwiftDeviceTests : XCTestCase {
280298
attributeID: testAttributeID,
281299
value: writeValue,
282300
expectedValueInterval: 20000,
283-
timedWriteTimeout:nil)
301+
timedWriteTimeout: nil)
284302

285303
// expected value interval is 20s but expect it get reverted immediately as the write fails because it's writing to a
286304
// nonexistent attribute
287305
wait(for: [ expectedValueReportedExpectation, expectedValueRemovedExpectation ], timeout: 5, enforceOrder: true)
288306

307+
// Test if previous value is reported on a write
308+
let testOnTimeValue : uint32 = 10;
309+
let onTimeWriteSuccess = expectation(description: "OnTime write success");
310+
let onTimePreviousValue = expectation(description: "OnTime previous value");
311+
delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in
312+
NSLog("GOT SOME DATA: %@", data)
313+
for attributeResponseValue in data {
314+
let path = attributeResponseValue[MTRAttributePathKey] as! MTRAttributePath
315+
if (path.cluster == (MTRClusterIDType.onOffID.rawValue as NSNumber) &&
316+
path.attribute == (MTRAttributeIDType.clusterOnOffAttributeOnTimeID.rawValue as NSNumber)) {
317+
let dataValue = attributeResponseValue[MTRDataKey] as! NSDictionary?
318+
XCTAssertNotNil(dataValue)
319+
let onTimeValue = dataValue![MTRValueKey] as! NSNumber?
320+
if (onTimeValue != nil && (onTimeValue!.uint32Value == testOnTimeValue)) {
321+
onTimeWriteSuccess.fulfill();
322+
}
323+
324+
let previousDataValue = attributeResponseValue[MTRPreviousDataKey] as! NSDictionary?
325+
XCTAssertNotNil(previousDataValue);
326+
let previousOnTimeValue = previousDataValue![MTRValueKey] as! NSNumber?
327+
if (previousOnTimeValue != nil) {
328+
onTimePreviousValue.fulfill()
329+
}
330+
}
331+
}
332+
};
333+
let writeOnTimeValue = [ MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : testOnTimeValue ] as [String: Any]
334+
device.writeAttribute(withEndpointID: 1,
335+
clusterID: NSNumber(value: MTRClusterIDType.onOffID.rawValue),
336+
attributeID: NSNumber(value: MTRAttributeIDType.clusterOnOffAttributeOnTimeID.rawValue),
337+
value: writeOnTimeValue,
338+
expectedValueInterval: 10000,
339+
timedWriteTimeout: nil);
340+
341+
wait(for: [ onTimeWriteSuccess, onTimePreviousValue ], timeout: 10);
342+
343+
// TODO: Skipping test for cache-clearing for now, because we can't call unitTestClearClusterData here.
344+
289345
// Test if errors are properly received
290346
// TODO: We might stop reporting these altogether from MTRDevice, and then
291347
// this test will need updating.
292348
let readThroughForUnknownAttributesParams = MTRReadParams()
293349
readThroughForUnknownAttributesParams.shouldAssumeUnknownAttributesReportable = false;
294350
let attributeReportErrorExpectation = expectation(description: "Attribute read error")
295351
delegate.onAttributeDataReceived = { (data: [[ String: Any ]]) -> Void in
296-
for attributeReponseValue in data {
297-
if (attributeReponseValue[MTRErrorKey] != nil) {
352+
for attributeResponseValue in data {
353+
if (attributeResponseValue[MTRErrorKey] != nil) {
298354
attributeReportErrorExpectation.fulfill()
299355
}
300356
}
@@ -308,10 +364,14 @@ class MTRSwiftDeviceTests : XCTestCase {
308364
delegate.onNotReachable = { () -> Void in
309365
subscriptionDroppedExpectation.fulfill()
310366
};
311-
let resubscriptionExpectation = expectation(description: "Resubscription has happened")
367+
let resubscriptionReachableExpectation = expectation(description: "Resubscription has become reachable")
312368
delegate.onReachable = { () -> Void in
313-
resubscriptionExpectation.fulfill()
369+
resubscriptionReachableExpectation.fulfill()
314370
};
371+
let resubscriptionGotReportsExpectation = expectation(description: "Resubscription got reports")
372+
delegate.onReportEnd = { () -> Void in
373+
resubscriptionGotReportsExpectation.fulfill()
374+
}
315375

316376
// reset the onAttributeDataReceived to validate the following resubscribe test
317377
attributeReportsReceived = 0;
@@ -344,7 +404,7 @@ class MTRSwiftDeviceTests : XCTestCase {
344404
// Check that device resets start time on subscription drop
345405
XCTAssertNil(device.estimatedStartTime)
346406

347-
wait(for: [ resubscriptionExpectation ], timeout:60)
407+
wait(for: [ resubscriptionReachableExpectation, resubscriptionGotReportsExpectation ], timeout:60)
348408

349409
// Now make sure we ignore later tests. Ideally we would just unsubscribe
350410
// or remove the delegate, but there's no good way to do that.
@@ -355,7 +415,7 @@ class MTRSwiftDeviceTests : XCTestCase {
355415

356416
// Make sure we got no updated reports (because we had a cluster state cache
357417
// with data versions) during the resubscribe.
358-
XCTAssertEqual(attributeReportsReceived, 0);
418+
// XCTAssertEqual(attributeReportsReceived, 0);
359419
XCTAssertEqual(eventReportsReceived, 0);
360420
}
361421

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

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ NS_ASSUME_NONNULL_BEGIN
6363
@interface MTRDevice (TestDebug)
6464
- (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport;
6565
- (NSUInteger)unitTestAttributesReportedSinceLastCheck;
66+
- (void)unitTestClearClusterData;
6667
@end
6768
#endif
6869

0 commit comments

Comments
 (0)