-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for acquiring multiple locks at once (#958)
* Add ASLocking which supports -tryLock and taking multiple locks safely * Better multi locking * Assert about lock set capacity
- Loading branch information
1 parent
9214e3c
commit 35d59ac
Showing
13 changed files
with
209 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// | ||
// ASLocking.h | ||
// Texture | ||
// | ||
// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. | ||
// 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 | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <pthread/sched.h> | ||
|
||
#import <AsyncDisplayKit/ASAssert.h> | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
#define kLockSetCapacity 32 | ||
|
||
/** | ||
* An extension of NSLocking that supports -tryLock. | ||
*/ | ||
@protocol ASLocking <NSLocking> | ||
|
||
/// Try to take lock without blocking. Returns whether the lock was taken. | ||
- (BOOL)tryLock; | ||
|
||
@end | ||
|
||
/** | ||
* A set of locks acquired during ASLockSequence. | ||
*/ | ||
typedef struct { | ||
unsigned count; | ||
CFTypeRef _Nullable locks[kLockSetCapacity]; | ||
} ASLockSet; | ||
|
||
/** | ||
* Declare a lock set that is automatically unlocked at the end of scope. | ||
* | ||
* We use this instead of a scope-locking macro because we want to be able | ||
* to step through the lock sequence block in the debugger. | ||
*/ | ||
#define ASScopedLockSet __unused ASLockSet __attribute__((cleanup(ASUnlockSet))) | ||
|
||
/** | ||
* A block that attempts to add a lock to a lock sequence. | ||
* Such a block is provided to the caller of ASLockSequence. | ||
* | ||
* Returns whether the lock was added. You should return | ||
* NO from your lock sequence body if it returns NO. | ||
* | ||
* For instance, you might write `return addLock(l1) && addLock(l2)`. | ||
* | ||
* @param lock The lock to attempt to add. | ||
* @return YES if the lock was added, NO otherwise. | ||
*/ | ||
typedef BOOL(^ASAddLockBlock)(id<ASLocking> lock); | ||
|
||
/** | ||
* A block that attempts to lock multiple locks in sequence. | ||
* Such a block is provided by the caller of ASLockSequence. | ||
* | ||
* The block may be run multiple times, if not all locks are immediately | ||
* available. Therefore the block should be idempotent. | ||
* | ||
* The block should attempt to invoke addLock multiple times with | ||
* different locks. It should return NO as soon as any addLock | ||
* operation fails. | ||
* | ||
* For instance, you might write `return addLock(l1) && addLock(l2)`. | ||
* | ||
* @param addLock A block you can call to attempt to add a lock. | ||
* @return YES if all locks were added, NO otherwise. | ||
*/ | ||
typedef BOOL(^ASLockSequenceBlock)(NS_NOESCAPE ASAddLockBlock addLock); | ||
|
||
/** | ||
* Unlock and release all of the locks in this lock set. | ||
*/ | ||
NS_INLINE void ASUnlockSet(ASLockSet *lockSet) { | ||
for (unsigned i = 0; i < lockSet->count; i++) { | ||
CFTypeRef lock = lockSet->locks[i]; | ||
[(__bridge id<ASLocking>)lock unlock]; | ||
CFRelease(lock); | ||
} | ||
} | ||
|
||
/** | ||
* Take multiple locks "simultaneously," avoiding deadlocks | ||
* caused by lock ordering. | ||
* | ||
* The block you provide should attempt to take a series of locks, | ||
* using the provided `addLock` block. As soon as any addLock fails, | ||
* you should return NO. | ||
* | ||
* For example: | ||
* ASLockSequence(^(ASAddLockBlock addLock) ^{ | ||
* return addLock(l0) && addLock(l1); | ||
* }); | ||
* | ||
* Note: This function doesn't protect from lock ordering deadlocks if | ||
* one of the locks is already locked (recursive.) Only locks taken | ||
* inside this function are guaranteed not to cause a deadlock. | ||
*/ | ||
NS_INLINE ASLockSet ASLockSequence(NS_NOESCAPE ASLockSequenceBlock body) | ||
{ | ||
__block ASLockSet locks = (ASLockSet){0}; | ||
BOOL (^addLock)(id<ASLocking>) = ^(id<ASLocking> obj) { | ||
|
||
// nil lock = ignore. | ||
if (!obj) { | ||
return YES; | ||
} | ||
|
||
// If they go over capacity, assert and return YES. | ||
// If we return NO, they will enter an infinite loop. | ||
if (locks.count == kLockSetCapacity) { | ||
ASDisplayNodeCFailAssert(@"Locking more than %d locks at once is not supported.", kLockSetCapacity); | ||
return YES; | ||
} | ||
|
||
if ([obj tryLock]) { | ||
locks.locks[locks.count++] = (__bridge_retained CFTypeRef)obj; | ||
return YES; | ||
} | ||
return NO; | ||
}; | ||
|
||
/** | ||
* Repeatedly try running their block, passing in our `addLock` | ||
* until it succeeds. If it fails, unlock all and yield the thread | ||
* to reduce spinning. | ||
*/ | ||
while (true) { | ||
if (body(addLock)) { | ||
// Success | ||
return locks; | ||
} else { | ||
ASUnlockSet(&locks); | ||
locks.count = 0; | ||
sched_yield(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* These Foundation classes already implement -tryLock. | ||
*/ | ||
|
||
@interface NSLock (ASLocking) <ASLocking> | ||
@end | ||
|
||
@interface NSRecursiveLock (ASLocking) <ASLocking> | ||
@end | ||
|
||
@interface NSConditionLock (ASLocking) <ASLocking> | ||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters