forked from TextureGroup/Texture
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathASLocking.h
158 lines (136 loc) · 4.16 KB
/
ASLocking.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//
// ASLocking.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: 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