FSTDispatchQueue.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <Foundation/Foundation.h>
  17. #include <atomic>
  18. #import "Firestore/Source/Util/FSTAssert.h"
  19. #import "Firestore/Source/Util/FSTDispatchQueue.h"
  20. NS_ASSUME_NONNULL_BEGIN
  21. /**
  22. * removeDelayedCallback is used by FSTDelayedCallback and so we pre-declare it before the rest of
  23. * the FSTDispatchQueue private interface.
  24. */
  25. @interface FSTDispatchQueue ()
  26. - (void)removeDelayedCallback:(FSTDelayedCallback *)callback;
  27. @end
  28. #pragma mark - FSTDelayedCallback
  29. /**
  30. * Represents a callback scheduled to be run in the future on an FSTDispatchQueue.
  31. *
  32. * It is created via [FSTDelayedCallback createAndScheduleWithQueue].
  33. *
  34. * Supports cancellation (via cancel) and early execution (via skipDelay).
  35. */
  36. @interface FSTDelayedCallback ()
  37. @property(nonatomic, strong, readonly) FSTDispatchQueue *queue;
  38. @property(nonatomic, assign, readonly) FSTTimerID timerID;
  39. @property(nonatomic, assign, readonly) NSTimeInterval targetTime;
  40. @property(nonatomic, copy) void (^callback)();
  41. /** YES if the callback has been run or canceled. */
  42. @property(nonatomic, getter=isDone) BOOL done;
  43. /**
  44. * Creates and returns an FSTDelayedCallback that has been scheduled on the provided queue with the
  45. * provided delay.
  46. *
  47. * @param queue The FSTDispatchQueue to run the callback on.
  48. * @param timerID A FSTTimerID identifying the type of the delayed callback.
  49. * @param delay The delay before the callback should be scheduled.
  50. * @param callback The callback block to run.
  51. * @return The created FSTDelayedCallback instance.
  52. */
  53. + (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
  54. timerID:(FSTTimerID)timerID
  55. delay:(NSTimeInterval)delay
  56. callback:(void (^)(void))callback;
  57. /**
  58. * Queues the callback to run immediately (if it hasn't already been run or canceled).
  59. */
  60. - (void)skipDelay;
  61. @end
  62. @implementation FSTDelayedCallback
  63. - (instancetype)initWithQueue:(FSTDispatchQueue *)queue
  64. timerID:(FSTTimerID)timerID
  65. targetTime:(NSTimeInterval)targetTime
  66. callback:(void (^)(void))callback {
  67. if (self = [super init]) {
  68. _queue = queue;
  69. _timerID = timerID;
  70. _targetTime = targetTime;
  71. _callback = callback;
  72. _done = NO;
  73. }
  74. return self;
  75. }
  76. + (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
  77. timerID:(FSTTimerID)timerID
  78. delay:(NSTimeInterval)delay
  79. callback:(void (^)(void))callback {
  80. NSTimeInterval targetTime = [[NSDate date] timeIntervalSince1970] + delay;
  81. FSTDelayedCallback *delayedCallback = [[FSTDelayedCallback alloc] initWithQueue:queue
  82. timerID:timerID
  83. targetTime:targetTime
  84. callback:callback];
  85. [delayedCallback startWithDelay:delay];
  86. return delayedCallback;
  87. }
  88. /**
  89. * Starts the timer. This is called immediately after construction by createAndScheduleWithQueue.
  90. */
  91. - (void)startWithDelay:(NSTimeInterval)delay {
  92. dispatch_time_t delayNs = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
  93. dispatch_after(delayNs, self.queue.queue, ^{
  94. [self.queue enterCheckedOperation:^{
  95. [self delayDidElapse];
  96. }];
  97. });
  98. }
  99. - (void)skipDelay {
  100. [self.queue dispatchAsyncAllowingSameQueue:^{
  101. [self delayDidElapse];
  102. }];
  103. }
  104. - (void)cancel {
  105. [self.queue verifyIsCurrentQueue];
  106. if (!self.isDone) {
  107. // PORTING NOTE: There's no way to actually cancel the dispatched callback, but it'll be a no-op
  108. // since we set done to YES.
  109. [self markDone];
  110. }
  111. }
  112. - (void)delayDidElapse {
  113. [self.queue verifyIsCurrentQueue];
  114. if (!self.isDone) {
  115. [self markDone];
  116. self.callback();
  117. }
  118. }
  119. /**
  120. * Marks this delayed callback as done, and notifies the FSTDispatchQueue that it should be removed.
  121. */
  122. - (void)markDone {
  123. self.done = YES;
  124. [self.queue removeDelayedCallback:self];
  125. }
  126. @end
  127. #pragma mark - FSTDispatchQueue
  128. @interface FSTDispatchQueue ()
  129. /**
  130. * Callbacks scheduled to be queued in the future. Callbacks are automatically removed after they
  131. * are run or canceled.
  132. */
  133. @property(nonatomic, strong, readonly) NSMutableArray<FSTDelayedCallback *> *delayedCallbacks;
  134. - (instancetype)initWithQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
  135. @end
  136. @implementation FSTDispatchQueue {
  137. /**
  138. * Flag set while an FSTDispatchQueue operation is currently executing. Used for assertion
  139. * sanity-checks.
  140. */
  141. std::atomic<bool> _operationInProgress;
  142. }
  143. + (instancetype)queueWith:(dispatch_queue_t)dispatchQueue {
  144. return [[FSTDispatchQueue alloc] initWithQueue:dispatchQueue];
  145. }
  146. - (instancetype)initWithQueue:(dispatch_queue_t)queue {
  147. if (self = [super init]) {
  148. _operationInProgress = false;
  149. _queue = queue;
  150. _delayedCallbacks = [NSMutableArray array];
  151. }
  152. return self;
  153. }
  154. - (void)verifyIsCurrentQueue {
  155. FSTAssert([self onTargetQueue],
  156. @"We are running on the wrong dispatch queue. Expected '%@' Actual: '%@'",
  157. [self targetQueueLabel], [self currentQueueLabel]);
  158. FSTAssert(_operationInProgress,
  159. @"verifyIsCurrentQueue called outside enterCheckedOperation on queue '%@'",
  160. [self currentQueueLabel]);
  161. }
  162. - (void)enterCheckedOperation:(void (^)(void))block {
  163. FSTAssert(!_operationInProgress,
  164. @"enterCheckedOperation may not be called when an operation is in progress");
  165. @try {
  166. _operationInProgress = true;
  167. [self verifyIsCurrentQueue];
  168. block();
  169. } @finally {
  170. _operationInProgress = false;
  171. }
  172. }
  173. - (void)dispatchAsync:(void (^)(void))block {
  174. FSTAssert(![self onTargetQueue] || !_operationInProgress,
  175. @"dispatchAsync called when we are already running on target dispatch queue '%@'",
  176. [self targetQueueLabel]);
  177. dispatch_async(self.queue, ^{
  178. [self enterCheckedOperation:block];
  179. });
  180. }
  181. - (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block {
  182. dispatch_async(self.queue, ^{
  183. [self enterCheckedOperation:block];
  184. });
  185. }
  186. - (void)dispatchSync:(void (^)(void))block {
  187. FSTAssert(![self onTargetQueue] || !_operationInProgress,
  188. @"dispatchSync called when we are already running on target dispatch queue '%@'",
  189. [self targetQueueLabel]);
  190. dispatch_sync(self.queue, ^{
  191. [self enterCheckedOperation:block];
  192. });
  193. }
  194. - (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay
  195. timerID:(FSTTimerID)timerID
  196. block:(void (^)(void))block {
  197. // While not necessarily harmful, we currently don't expect to have multiple callbacks with the
  198. // same timerID in the queue, so defensively reject them.
  199. FSTAssert(![self containsDelayedCallbackWithTimerID:timerID],
  200. @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID);
  201. FSTDelayedCallback *delayedCallback = [FSTDelayedCallback createAndScheduleWithQueue:self
  202. timerID:timerID
  203. delay:delay
  204. callback:block];
  205. [self.delayedCallbacks addObject:delayedCallback];
  206. return delayedCallback;
  207. }
  208. - (BOOL)containsDelayedCallbackWithTimerID:(FSTTimerID)timerID {
  209. NSUInteger matchIndex = [self.delayedCallbacks
  210. indexOfObjectPassingTest:^BOOL(FSTDelayedCallback *obj, NSUInteger idx, BOOL *stop) {
  211. return obj.timerID == timerID;
  212. }];
  213. return matchIndex != NSNotFound;
  214. }
  215. - (void)runDelayedCallbacksUntil:(FSTTimerID)lastTimerID {
  216. dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0);
  217. [self dispatchAsync:^{
  218. FSTAssert(lastTimerID == FSTTimerIDAll || [self containsDelayedCallbackWithTimerID:lastTimerID],
  219. @"Attempted to run callbacks until missing timer ID: %ld",
  220. (unsigned long)lastTimerID);
  221. [self sortDelayedCallbacks];
  222. for (FSTDelayedCallback *callback in self.delayedCallbacks) {
  223. [callback skipDelay];
  224. if (lastTimerID != FSTTimerIDAll && callback.timerID == lastTimerID) {
  225. break;
  226. }
  227. }
  228. // Now that the callbacks are queued, we want to enqueue an additional item to release the
  229. // 'done' semaphore.
  230. [self dispatchAsyncAllowingSameQueue:^{
  231. dispatch_semaphore_signal(doneSemaphore);
  232. }];
  233. }];
  234. dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER);
  235. }
  236. // NOTE: For performance we could store the callbacks sorted (e.g. using std::priority_queue),
  237. // but this sort only happens in tests (if runDelayedCallbacksUntil: is called), and the size
  238. // is guaranteed to be small since we don't allow duplicate TimerIds (of which there are only 4).
  239. - (void)sortDelayedCallbacks {
  240. // We want to run callbacks in the same order they'd run if they ran naturally.
  241. [self.delayedCallbacks
  242. sortUsingComparator:^NSComparisonResult(FSTDelayedCallback *a, FSTDelayedCallback *b) {
  243. return a.targetTime < b.targetTime
  244. ? NSOrderedAscending
  245. : a.targetTime > b.targetTime ? NSOrderedDescending : NSOrderedSame;
  246. }];
  247. }
  248. /** Called by FSTDelayedCallback when a callback is run or canceled. */
  249. - (void)removeDelayedCallback:(FSTDelayedCallback *)callback {
  250. NSUInteger index = [self.delayedCallbacks indexOfObject:callback];
  251. FSTAssert(index != NSNotFound, @"Delayed callback not found.");
  252. [self.delayedCallbacks removeObjectAtIndex:index];
  253. }
  254. #pragma mark - Private Methods
  255. - (NSString *)currentQueueLabel {
  256. return [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
  257. }
  258. - (NSString *)targetQueueLabel {
  259. return [NSString stringWithUTF8String:dispatch_queue_get_label(self.queue)];
  260. }
  261. - (BOOL)onTargetQueue {
  262. return [[self currentQueueLabel] isEqualToString:[self targetQueueLabel]];
  263. }
  264. @end
  265. NS_ASSUME_NONNULL_END