Skip to content

Commit

Permalink
Darwin: Deprecate MTRAsyncCallbackQueue and clone into MTRAsyncWorkQu…
Browse files Browse the repository at this point in the history
…eue (#29356)

MTRAsyncCallbackQueue was accidentally made part of the public API, which
is now constraining our ability to refactor its API. This change "renames"
the class to MTRAsyncWorkQueue (and similar renames for related types), but
leaves the existing MTRAsyncCallbackQueue class in place. (More precisely,
only the parts of it that were public are retained.)

Also disable TAPI for Debug builds so we can easily unit-test internal
parts of the framework.
  • Loading branch information
ksperling-apple authored Sep 21, 2023
1 parent d64fd13 commit bfa9b4c
Show file tree
Hide file tree
Showing 14 changed files with 1,372 additions and 792 deletions.
40 changes: 4 additions & 36 deletions src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,67 +15,35 @@
* limitations under the License.
*/

#import <Foundation/Foundation.h>
#import <Matter/MTRDefines.h>

NS_ASSUME_NONNULL_BEGIN

@class MTRAsyncCallbackQueueWorkItem;

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

// MTRAsyncCallbackQueue high level description
// The MTRAsyncCallbackQueue was made to call one readyHandler
// block at a time asynchronously, and the readyHandler is
// expected to start/schedule a task. When the task finishes
// asynchronously in the future (at any time, from any queue
// or thread), it is expected to ask the workItem object to
// either endWork or retryWork.

// Sequence of steps when queuing a work item:
// - Create MTRAsyncCallbackQueueWorkItem object
// - Create ready handler block (MTRAsyncCallbackReadyHandler)
// - block is called when it's the WorkItem's turn to do work
// - its body is to perform a task that is expected to end asynchronously in the future
// - at the end of work, call on the work item object:
// - endWork for success or failure
// - retryWork for temporary failures
// - Set the readyHandler block on the WorkItem object
// - Call enqueueWorkItem on a MTRAsyncCallbackQueue

// A serial one-at-a-time queue for performing work items
MTR_NEWLY_DEPRECATED("This class was not intended to be part of the public Matter API")
@interface MTRAsyncCallbackWorkQueue : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

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

// Called by the work queue owner to clean up and cancel work items
- (void)invalidate;

// Work items may be enqueued from any queue or thread
// Note: Once a work item is enqueued, its handlers cannot be modified
- (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item;
@end

// An item in the work queue
MTR_NEWLY_DEPRECATED("This class was not intended to be part of the public Matter API")
@interface MTRAsyncCallbackQueueWorkItem : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

// Both readyHandler and cancelHander will be called on the queue given to initWithQueue
- (instancetype)initWithQueue:(dispatch_queue_t)queue;

@property (nonatomic, strong) MTRAsyncCallbackReadyHandler readyHandler;
@property (nonatomic, strong) dispatch_block_t cancelHandler;

// Called by the creater of the work item when async work is done and should
// be removed from the queue. The work queue will run the next work item.
// Note: This must only be called from within the readyHandler
- (void)endWork;

// Called by the creater of the work item when async work should be retried.
// The work queue will call this workItem's readyHandler again.
// Note: This must only be called from within the readyHandler
- (void)retryWork;
@end

Expand Down
64 changes: 1 addition & 63 deletions src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
#import <dispatch/dispatch.h>
#import <os/lock.h>

#import "MTRAsyncCallbackWorkQueue_Internal.h"
#import "MTRLogging_Internal.h"
#import <Matter/MTRAsyncCallbackWorkQueue.h>

#pragma mark - Class extensions

Expand Down Expand Up @@ -62,7 +62,6 @@ - (instancetype)initWithContext:(id)context queue:(dispatch_queue_t)queue
_context = context;
_queue = queue;
_items = [NSMutableArray array];
MTR_LOG_INFO("MTRAsyncCallbackWorkQueue init for context %@", context);
}
return self;
}
Expand Down Expand Up @@ -103,8 +102,6 @@ - (void)invalidate
_items = nil;
os_unfair_lock_unlock(&_lock);

MTR_LOG_INFO(
"MTRAsyncCallbackWorkQueue invalidate for context %@ items count: %lu", _context, (unsigned long) invalidateItems.count);
for (MTRAsyncCallbackQueueWorkItem * item in invalidateItems) {
[item cancel];
}
Expand Down Expand Up @@ -169,50 +166,10 @@ - (void)_callNextReadyWorkItem
self.runningWorkItemCount = 1;

MTRAsyncCallbackQueueWorkItem * workItem = self.items.firstObject;

// Check if batching is possible or needed. Only ask work item to batch once for simplicity
if (workItem.batchable && workItem.batchingHandler && (workItem.retryCount == 0)) {
while (self.items.count >= 2) {
MTRAsyncCallbackQueueWorkItem * nextWorkItem = self.items[1];
if (!nextWorkItem.batchable || (nextWorkItem.batchingID != workItem.batchingID)) {
// next item is not eligible to merge with this one
break;
}

BOOL fullyMerged = NO;
workItem.batchingHandler(workItem.batchableData, nextWorkItem.batchableData, &fullyMerged);
if (!fullyMerged) {
// We can't remove the next work item, so we can't merge anything else into this one.
break;
}

[self.items removeObjectAtIndex:1];
}
}

[workItem callReadyHandlerWithContext:self.context];
}
}

- (BOOL)isDuplicateForTypeID:(NSUInteger)opaqueDuplicateTypeID workItemData:(id)opaqueWorkItemData
{
os_unfair_lock_lock(&_lock);
// Start from the last item
for (NSUInteger i = self.items.count; i > 0; i--) {
MTRAsyncCallbackQueueWorkItem * item = self.items[i - 1];
BOOL isDuplicate = NO;
BOOL stop = NO;
if (item.supportsDuplicateCheck && (item.duplicateTypeID == opaqueDuplicateTypeID) && item.duplicateCheckHandler) {
item.duplicateCheckHandler(opaqueWorkItemData, &isDuplicate, &stop);
if (stop) {
os_unfair_lock_unlock(&_lock);
return isDuplicate;
}
}
}
os_unfair_lock_unlock(&_lock);
return NO;
}
@end

@implementation MTRAsyncCallbackQueueWorkItem
Expand Down Expand Up @@ -319,23 +276,4 @@ - (void)cancel
}
}

- (void)setBatchingID:(NSUInteger)opaqueBatchingID
data:(id)opaqueBatchableData
handler:(MTRAsyncCallbackBatchingHandler)batchingHandler
{
os_unfair_lock_lock(&self->_lock);
_batchable = YES;
_batchingID = opaqueBatchingID;
_batchableData = opaqueBatchableData;
_batchingHandler = batchingHandler;
os_unfair_lock_unlock(&self->_lock);
}

- (void)setDuplicateTypeID:(NSUInteger)opaqueDuplicateTypeID handler:(MTRAsyncCallbackDuplicateCheckHandler)duplicateCheckHandler
{
_supportsDuplicateCheck = YES;
_duplicateTypeID = opaqueDuplicateTypeID;
_duplicateCheckHandler = duplicateCheckHandler;
}

@end
84 changes: 84 additions & 0 deletions src/darwin/Framework/CHIP/MTRAsyncWorkQueue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
*
* 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 "MTRDefines_Internal.h"

NS_ASSUME_NONNULL_BEGIN

@class MTRAsyncWorkItem;

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

// MTRAsyncWorkQueue high level description
// The MTRAsyncWorkQueue was made to call one readyHandler
// block at a time asynchronously, and the readyHandler is
// expected to start/schedule a task. When the task finishes
// asynchronously in the future (at any time, from any queue
// or thread), it is expected to ask the workItem object to
// either endWork or retryWork.

// Sequence of steps when queuing a work item:
// - Create MTRAsyncWorkItem object
// - Create ready handler block (MTRAsyncWorkReadyHandler)
// - block is called when it's the WorkItem's turn to do work
// - its body is to perform a task that is expected to end asynchronously in the future
// - at the end of work, call on the work item object:
// - endWork for success or failure
// - retryWork for temporary failures
// - Set the readyHandler block on the WorkItem object
// - Call enqueueWorkItem on a MTRAsyncWorkQueue

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

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

// Called by the work queue owner to clean up and cancel work items
- (void)invalidate;

// Work items may be enqueued from any queue or thread
// Note: Once a work item is enqueued, its handlers cannot be modified
- (void)enqueueWorkItem:(MTRAsyncWorkItem *)item;
@end

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

// Both readyHandler and cancelHander will be called on the queue given to initWithQueue
- (instancetype)initWithQueue:(dispatch_queue_t)queue;
@property (nonatomic, strong) MTRAsyncWorkReadyHandler readyHandler;
@property (nonatomic, strong) dispatch_block_t cancelHandler;

// Called by the creater of the work item when async work is done and should
// be removed from the queue. The work queue will run the next work item.
// Note: This must only be called from within the readyHandler
- (void)endWork;

// Called by the creater of the work item when async work should be retried.
// The work queue will call this workItem's readyHandler again.
// Note: This must only be called from within the readyHandler
- (void)retryWork;
@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit bfa9b4c

Please sign in to comment.