diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h index bd3fcbae4f7099..b9d7030f9ff79e 100644 --- a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h @@ -84,6 +84,11 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac // Ensure we release the ReadClient before we tear down anything else, // so it can call our OnDeallocatePaths properly. mReadClient = nullptr; + + // Make sure the block isn't run after object destruction + if (mInterimReportBlock) { + dispatch_block_cancel(mInterimReportBlock); + } } chip::app::BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; } @@ -103,9 +108,11 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac // be immediately followed by OnDone and we want to do the deletion there. void ReportError(CHIP_ERROR aError, bool aCancelSubscription = true); - void ReportAttributes(NSArray * attributeReports); + void ReportCurrentData(); - void ReportEvents(NSArray * eventReports); + // Called at attribute/event report time to queue a block to report on the Matter queue so that for multi-packet reports, this + // block is run and reports in batch. No-op if the block is already queued. + void QueueInterimReport(); private: void OnReportBegin() override; @@ -170,6 +177,7 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac std::unique_ptr mClusterStateCache; bool mHaveQueuedDeletion = false; OnDoneHandler _Nullable mOnDoneHandler = nil; + dispatch_block_t mInterimReportBlock = nil; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm index 7c67e645954e99..a10cd860d8cdb7 100644 --- a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm @@ -37,32 +37,55 @@ { __block NSArray * attributeReports = mAttributeReports; mAttributeReports = nil; + auto attributeCallback = mAttributeReportCallback; __block NSArray * eventReports = mEventReports; mEventReports = nil; + auto eventCallback = mEventReportCallback; - ReportAttributes(attributeReports); - ReportEvents(eventReports); -} - -void MTRBaseSubscriptionCallback::ReportAttributes(NSArray * attributeReports) -{ - auto attributeCallback = mAttributeReportCallback; if (attributeCallback != nil && attributeReports.count) { attributeCallback(attributeReports); } -} -void MTRBaseSubscriptionCallback::ReportEvents(NSArray * eventReports) -{ - auto eventCallback = mEventReportCallback; if (eventCallback != nil && eventReports.count) { eventCallback(eventReports); } } +void MTRBaseSubscriptionCallback::QueueInterimReport() +{ + if (mInterimReportBlock) { + return; + } + + // __block auto * myself = this; + mInterimReportBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{ + mInterimReportBlock = nil; + ReportData(); + // Allocate reports arrays to continue accumulation + mAttributeReports = [NSMutableArray new]; + mEventReports = [NSMutableArray new]; + // myself->ReportCurrentData(); + }); + + dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), mInterimReportBlock); +} + +void MTRBaseSubscriptionCallback::ReportCurrentData() +{ + mInterimReportBlock = nil; + ReportData(); + // Allocate reports arrays to continue accumulation + mAttributeReports = [NSMutableArray new]; + mEventReports = [NSMutableArray new]; +} + void MTRBaseSubscriptionCallback::OnReportEnd() { + if (mInterimReportBlock) { + dispatch_block_cancel(mInterimReportBlock); + mInterimReportBlock = nil; + } ReportData(); if (mReportEndHandler) { mReportEndHandler(); diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 25795ecdcfb70b..bd94b1053b36b3 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -187,6 +187,10 @@ @interface MTRDevice () @end +@protocol MTRDeviceUnitTestDelegate +- (void)unitTestReportEndForDevice:(MTRDevice *)device; +@end + @implementation MTRDevice - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller @@ -429,6 +433,15 @@ - (void)_handleReportEnd { os_unfair_lock_lock(&self->_lock); _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; +// For unit testing only +#ifdef DEBUG + id delegate = _weakDelegate.strongObject; + if (delegate && [delegate respondsToSelector:@selector(unitTestReportEndForDevice:)]) { + dispatch_async(_delegateQueue, ^{ + [delegate unitTestReportEndForDevice:self]; + }); + } +#endif os_unfair_lock_unlock(&self->_lock); } @@ -1527,24 +1540,27 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID } MTREventPath * eventPath = [[MTREventPath alloc] initWithPath:aEventHeader.mPath]; - NSDictionary * eventReport; if (apStatus != nullptr) { [mEventReports addObject:@ { MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForIMStatus:*apStatus] }]; } else if (apData == nullptr) { - eventReport = @ { MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] }; + [mEventReports addObject:@ { + MTREventPathKey : eventPath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] + }]; } else { id value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData); if (value == nil) { MTR_LOG_ERROR("Failed to decode event data for path %@", eventPath); - eventReport = @ { + [mEventReports addObject:@ { MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_DECODE_FAILED], - }; + }]; } else { - eventReport = [MTRBaseDevice eventReportForHeader:aEventHeader andData:value]; + [mEventReports addObject:[MTRBaseDevice eventReportForHeader:aEventHeader andData:value]]; } } - ReportEvents(@[ eventReport ]); + + QueueInterimReport(); } void SubscriptionCallback::OnAttributeData( @@ -1562,25 +1578,26 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID } MTRAttributePath * attributePath = [[MTRAttributePath alloc] initWithPath:aPath]; - NSDictionary * attributeReport; if (aStatus.mStatus != Status::Success) { - attributeReport = @ { MTRAttributePathKey : attributePath, MTRErrorKey : [MTRError errorForIMStatus:aStatus] }; + [mAttributeReports addObject:@ { MTRAttributePathKey : attributePath, MTRErrorKey : [MTRError errorForIMStatus:aStatus] }]; } else if (apData == nullptr) { - attributeReport = - @ { MTRAttributePathKey : attributePath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] }; + [mAttributeReports addObject:@ { + MTRAttributePathKey : attributePath, + MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] + }]; } else { id value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData); if (value == nil) { MTR_LOG_ERROR("Failed to decode attribute data for path %@", attributePath); - attributeReport = @ { + [mAttributeReports addObject:@ { MTRAttributePathKey : attributePath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_DECODE_FAILED], - }; + }]; } else { - attributeReport = @ { MTRAttributePathKey : attributePath, MTRDataKey : value }; + [mAttributeReports addObject:@ { MTRAttributePathKey : attributePath, MTRDataKey : value }]; } } - ReportAttributes(@[ attributeReport ]); + QueueInterimReport(); } } // anonymous namespace diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 95f4aab4d5d30c..2fa2800e0dcf8c 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -122,6 +122,7 @@ @interface MTRDeviceTestDelegate : NSObject @property (nonatomic, nullable) dispatch_block_t onNotReachable; @property (nonatomic, nullable) MTRDeviceTestDelegateDataHandler onAttributeDataReceived; @property (nonatomic, nullable) MTRDeviceTestDelegateDataHandler onEventDataReceived; +@property (nonatomic, nullable) dispatch_block_t onReportEnd; @end @implementation MTRDeviceTestDelegate @@ -148,6 +149,13 @@ - (void)device:(MTRDevice *)device receivedEventReport:(NSArray *> * data) { attributeReportsReceived += data.count; };