Skip to content

Commit 3169617

Browse files
jtung-applepull[bot]
authored andcommitted
[Darwin] MTRAsyncCallbackWorkQueue tsan fix and API strengthening (#26800)
1 parent ef81a38 commit 3169617

File tree

3 files changed

+68
-13
lines changed

3 files changed

+68
-13
lines changed

src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.h

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ typedef void (^MTRAsyncCallbackReadyHandler)(id context, NSUInteger retryCount);
5454
- (void)invalidate;
5555

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

5960
// TODO: Add a "set concurrency width" method to allow for more than 1 work item at a time
@@ -71,10 +72,12 @@ typedef void (^MTRAsyncCallbackReadyHandler)(id context, NSUInteger retryCount);
7172

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

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

src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm

+65-10
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ - (void)retryWork:(MTRAsyncCallbackQueueWorkItem *)workItem;
4141
@end
4242

4343
@interface MTRAsyncCallbackQueueWorkItem ()
44+
@property (nonatomic, readonly) os_unfair_lock lock;
4445
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
4546
@property (nonatomic, readwrite) NSUInteger retryCount;
4647
@property (nonatomic, strong) MTRAsyncCallbackWorkQueue * workQueue;
48+
@property (nonatomic, readonly) BOOL enqueued;
4749
// Called by the queue
50+
- (void)markedEnqueued;
4851
- (void)callReadyHandlerWithContext:(id)context;
4952
- (void)cancel;
5053
@end
@@ -72,6 +75,13 @@ - (NSString *)description
7275

7376
- (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item
7477
{
78+
if (item.enqueued) {
79+
MTR_LOG_ERROR("MTRAsyncCallbackWorkQueue enqueueWorkItem: item cannot be enqueued twice");
80+
return;
81+
}
82+
83+
[item markedEnqueued];
84+
7585
os_unfair_lock_lock(&_lock);
7686
item.workQueue = self;
7787
[self.items addObject:item];
@@ -163,12 +173,14 @@ @implementation MTRAsyncCallbackQueueWorkItem
163173
- (instancetype)initWithQueue:(dispatch_queue_t)queue
164174
{
165175
if (self = [super init]) {
176+
_lock = OS_UNFAIR_LOCK_INIT;
166177
_queue = queue;
167178
}
168179
return self;
169180
}
170181

171-
- (void)invalidate
182+
// assume lock is held
183+
- (void)_invalidate
172184
{
173185
// Make sure we don't leak via handlers that close over us, as ours must.
174186
// This is a bit odd, since these are supposed to be non-nullable
@@ -181,6 +193,38 @@ - (void)invalidate
181193
_cancelHandler = nil;
182194
}
183195

196+
- (void)invalidate
197+
{
198+
os_unfair_lock_lock(&_lock);
199+
[self _invalidate];
200+
os_unfair_lock_unlock(&_lock);
201+
}
202+
203+
- (void)markedEnqueued
204+
{
205+
os_unfair_lock_lock(&_lock);
206+
_enqueued = YES;
207+
os_unfair_lock_unlock(&_lock);
208+
}
209+
210+
- (void)setReadyHandler:(MTRAsyncCallbackReadyHandler)readyHandler
211+
{
212+
os_unfair_lock_lock(&_lock);
213+
if (!_enqueued) {
214+
_readyHandler = readyHandler;
215+
}
216+
os_unfair_lock_unlock(&_lock);
217+
}
218+
219+
- (void)setCancelHandler:(dispatch_block_t)cancelHandler
220+
{
221+
os_unfair_lock_lock(&_lock);
222+
if (!_enqueued) {
223+
_cancelHandler = cancelHandler;
224+
}
225+
os_unfair_lock_unlock(&_lock);
226+
}
227+
184228
- (void)endWork
185229
{
186230
[self.workQueue endWork:self];
@@ -196,24 +240,35 @@ - (void)retryWork
196240
- (void)callReadyHandlerWithContext:(id)context
197241
{
198242
dispatch_async(self.queue, ^{
199-
if (self.readyHandler == nil) {
243+
os_unfair_lock_lock(&self->_lock);
244+
MTRAsyncCallbackReadyHandler readyHandler = self->_readyHandler;
245+
NSUInteger retryCount = self->_retryCount;
246+
if (readyHandler) {
247+
self->_retryCount++;
248+
}
249+
os_unfair_lock_unlock(&self->_lock);
250+
251+
if (readyHandler == nil) {
200252
// Nothing to do here.
201253
[self endWork];
202254
} else {
203-
self.readyHandler(context, self.retryCount);
204-
self.retryCount++;
255+
readyHandler(context, retryCount);
205256
}
206257
});
207258
}
208259

209260
// Called by the work queue
210261
- (void)cancel
211262
{
212-
dispatch_async(self.queue, ^{
213-
if (self.cancelHandler != nil) {
214-
self.cancelHandler();
215-
}
216-
[self invalidate];
217-
});
263+
os_unfair_lock_lock(&self->_lock);
264+
dispatch_block_t cancelHandler = self->_cancelHandler;
265+
[self _invalidate];
266+
os_unfair_lock_unlock(&self->_lock);
267+
268+
if (cancelHandler) {
269+
dispatch_async(self.queue, ^{
270+
cancelHandler();
271+
});
272+
}
218273
}
219274
@end

src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue_Internal.h

-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ NS_ASSUME_NONNULL_BEGIN
2626
@interface MTRAsyncCallbackWorkQueue ()
2727
// The MTRDevice object is only held and passed back as a reference and is opaque to the queue
2828
- (instancetype)initWithContext:(id _Nullable)context queue:(dispatch_queue_t)queue;
29-
30-
// Called by DeviceController at device clean up time
31-
- (void)invalidate;
3229
@end
3330

3431
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)