Skip to content

Commit

Permalink
Issue 18505 - Darwin: synchronous API part 1: basic structure and ser…
Browse files Browse the repository at this point in the history
…ial read (#20519)
  • Loading branch information
jtung-apple authored Aug 3, 2022
1 parent 3deee28 commit 83fb7fd
Show file tree
Hide file tree
Showing 30 changed files with 31,495 additions and 1,627 deletions.
1 change: 1 addition & 0 deletions .restyled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ exclude:
- "examples/chef/sample_app_util/test_files/*.yaml"
- "examples/chef/zzz_generated/**/*"
- "src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm" # https://github.com/project-chip/connectedhomeip/issues/20236
- "src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm"


changed_paths:
Expand Down
61 changes: 61 additions & 0 deletions src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
*
* Copyright (c) 2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@class MTRAsyncCallbackQueueWorkItem;

typedef void (^MTRAsyncCallbackReadyHandler)(id context, NSUInteger retryCount);

// How to queue a new work item:
// - Create MTRAsyncCallbackQueueWorkItem object
// - Create ready handler block (MTRAsyncCallbackReadyHandler)
// - block is called when it's the work item's turn to do work
// - its body is to do work with the device
// - at the end of work, call on the work item object:
// - endWork for success or failure
// - retryWork for temporary failures
// - Set the work handler block to the Item object
// - Call enqueueWorkItem on the MTRDevice's work queue property

// A serial one-at-a-time queue for performing work items
@interface MTRAsyncCallbackWorkQueue : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

- (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item;

// TODO: Add a "set concurrency width" method to allow for more than 1 work item at a time
@end

// An item in the work queue
@interface MTRAsyncCallbackQueueWorkItem : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

- (instancetype)initWithQueue:(dispatch_queue_t)queue;
@property (nonatomic, strong) MTRAsyncCallbackReadyHandler readyHandler;
@property (nonatomic, strong) dispatch_block_t cancelHandler;

// Called by Cluster object's after async work is done
- (void)endWork;
- (void)retryWork;
@end

NS_ASSUME_NONNULL_END
197 changes: 197 additions & 0 deletions src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
*
* Copyright (c) 2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <dispatch/dispatch.h>
#import <os/lock.h>

#import "MTRAsyncCallbackWorkQueue_Internal.h"
#import "MTRLogging.h"

#pragma mark - Class extensions

@interface MTRAsyncCallbackWorkQueue ()
@property (nonatomic, readonly) os_unfair_lock lock;
@property (nonatomic, strong, readonly) id context;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) NSMutableArray<MTRAsyncCallbackQueueWorkItem *> * items;
@property (nonatomic, readwrite) NSUInteger runningWorkItemCount;

// For WorkItem's use only - the parameter is for sanity check
- (void)endWork:(MTRAsyncCallbackQueueWorkItem *)workItem;
- (void)retryWork:(MTRAsyncCallbackQueueWorkItem *)workItem;
@end

@interface MTRAsyncCallbackQueueWorkItem ()
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, readwrite) NSUInteger retryCount;
@property (nonatomic, strong) MTRAsyncCallbackWorkQueue * workQueue;
// Called by the queue
- (void)callReadyHandlerWithContext:(id)context;
- (void)cancel;
@end

#pragma mark - Class implementations

@implementation MTRAsyncCallbackWorkQueue
- (instancetype)initWithContext:(id)context queue:(dispatch_queue_t)queue
{
if (self = [super init]) {
_lock = OS_UNFAIR_LOCK_INIT;
_context = context;
_queue = queue;
_items = [NSMutableArray array];
}
return self;
}

- (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item
{
os_unfair_lock_lock(&_lock);
item.workQueue = self;
[self.items addObject:item];

[self _callNextReadyWorkItem];
os_unfair_lock_unlock(&_lock);
}

- (void)invalidate
{
os_unfair_lock_lock(&_lock);
NSMutableArray * invalidateItems = _items;
_items = nil;
os_unfair_lock_unlock(&_lock);

for (MTRAsyncCallbackQueueWorkItem * item in invalidateItems) {
[item cancel];
}
[invalidateItems removeAllObjects];
}

- (void)endWork:(MTRAsyncCallbackQueueWorkItem *)workItem
{
os_unfair_lock_lock(&_lock);
// sanity check if running
if (!self.runningWorkItemCount) {
// something is wrong with state - nothing is currently running
os_unfair_lock_unlock(&_lock);
MTR_LOG_ERROR("endWork: no work is running on work queue");
return;
}

// sanity check the same work item is running
// when "concurrency width" is implemented need to check first N items
MTRAsyncCallbackQueueWorkItem * firstWorkItem = self.items.firstObject;
if (firstWorkItem != workItem) {
// something is wrong with this work item - should not be currently running
os_unfair_lock_unlock(&_lock);
MTR_LOG_ERROR("endWork: work item is not first on work queue");
return;
}

// since work is done, remove from queue and call ready on the next item
[self.items removeObjectAtIndex:0];

// when "concurrency width" is implemented this will be decremented instead
self.runningWorkItemCount = 0;
[self _callNextReadyWorkItem];
os_unfair_lock_unlock(&_lock);
}

- (void)retryWork:(MTRAsyncCallbackQueueWorkItem *)workItem
{
// reset BOOL and call again
os_unfair_lock_lock(&_lock);
// sanity check if running
if (!self.runningWorkItemCount) {
// something is wrong with state - nothing is currently running
os_unfair_lock_unlock(&_lock);
MTR_LOG_ERROR("retryWork: no work is running on work queue");
return;
}

// sanity check the same work item is running
// when "concurrency width" is implemented need to check first N items
MTRAsyncCallbackQueueWorkItem * firstWorkItem = self.items.firstObject;
if (firstWorkItem != workItem) {
// something is wrong with this work item - should not be currently running
os_unfair_lock_unlock(&_lock);
MTR_LOG_ERROR("retryWork: work item is not first on work queue");
return;
}

// when "concurrency width" is implemented this will be decremented instead
self.runningWorkItemCount = 0;
[self _callNextReadyWorkItem];
os_unfair_lock_unlock(&_lock);
}

// assume lock is held while calling this
- (void)_callNextReadyWorkItem
{
// when "concurrency width" is implemented this will be checked against the width
if (self.runningWorkItemCount) {
// can't run next work item until the current one is done
return;
}

// when "concurrency width" is implemented this will be incremented instead
self.runningWorkItemCount = 1;

MTRAsyncCallbackQueueWorkItem * workItem = self.items.firstObject;
[workItem callReadyHandlerWithContext:self.context];
}
@end

@implementation MTRAsyncCallbackQueueWorkItem

- (instancetype)initWithQueue:(dispatch_queue_t)queue
{
if (self = [super init]) {
_queue = queue;
}
return self;
}

// Called by Cluster object's after async work is done
- (void)endWork
{
[self.workQueue endWork:self];
}

// Called by Cluster object's after async work is done
- (void)retryWork
{
[self.workQueue retryWork:self];
}

// Called by the work queue
- (void)callReadyHandlerWithContext:(id)context
{
dispatch_async(self.queue, ^{
self.readyHandler(context, self.retryCount);
self.retryCount++;
});
}

// Called by the work queue
- (void)cancel
{
dispatch_async(self.queue, ^{
self.cancelHandler();
});
}
@end
34 changes: 34 additions & 0 deletions src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue_Internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
*
* Copyright (c) 2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

#import "MTRAsyncCallbackWorkQueue.h"

NS_ASSUME_NONNULL_BEGIN

@class MTRDevice;

@interface MTRAsyncCallbackWorkQueue ()
// The MTRDevice object is only held and passed back as a reference and is opaque to the queue
- (instancetype)initWithContext:(id _Nullable)context queue:(dispatch_queue_t)queue;

// Called by DeviceController at device clean up time
- (void)invalidate;
@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion src/darwin/Framework/CHIP/MTRAttributeCacheContainer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static CHIP_ERROR AppendAttibuteValueToArray(
chip::TLV::TLVReader reader;
CHIP_ERROR err = cache->Get(path, reader);
if (err == CHIP_NO_ERROR) {
id obj = NSObjectFromCHIPTLV(&reader);
id obj = MTRDecodeDataValueDictionaryFromCHIPTLV(&reader);
if (obj) {
[array addObject:@ { MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:path], MTRDataKey : obj }];
return CHIP_NO_ERROR;
Expand Down
14 changes: 8 additions & 6 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ NS_ASSUME_NONNULL_BEGIN
* MTRDataKey : Data-value NSDictionary object.
*/
typedef void (^MTRDeviceResponseHandler)(NSArray<NSDictionary<NSString *, id> *> * _Nullable values, NSError * _Nullable error);
typedef void (^MTRDeviceReportHandler)(NSArray * values);
typedef void (^MTRDeviceErrorHandler)(NSError * error);

extern NSString * const MTRAttributePathKey;
extern NSString * const MTRCommandPathKey;
Expand Down Expand Up @@ -129,12 +131,12 @@ extern NSString * const MTRArrayValueType;
- (void)subscribeWithQueue:(dispatch_queue_t)queue
minInterval:(uint16_t)minInterval
maxInterval:(uint16_t)maxInterval
params:(nullable MTRSubscribeParams *)params
params:(MTRSubscribeParams * _Nullable)params
cacheContainer:(MTRAttributeCacheContainer * _Nullable)attributeCacheContainer
attributeReportHandler:(nullable void (^)(NSArray * value))attributeReportHandler
eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler
errorHandler:(void (^)(NSError * error))errorHandler
subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler;
attributeReportHandler:(MTRDeviceReportHandler _Nullable)attributeReportHandler
eventReportHandler:(MTRDeviceReportHandler _Nullable)eventReportHandler
errorHandler:(MTRDeviceErrorHandler)errorHandler
subscriptionEstablished:(dispatch_block_t _Nullable)subscriptionEstablishedHandler;

/**
* Read attribute in a designated attribute path
Expand Down Expand Up @@ -211,7 +213,7 @@ extern NSString * const MTRArrayValueType;

@end

@interface MTRAttributePath : NSObject
@interface MTRAttributePath : NSObject <NSCopying>
@property (nonatomic, readonly, strong, nonnull) NSNumber * endpoint;
@property (nonatomic, readonly, strong, nonnull) NSNumber * cluster;
@property (nonatomic, readonly, strong, nonnull) NSNumber * attribute;
Expand Down
Loading

0 comments on commit 83fb7fd

Please sign in to comment.