OSSTask.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /*
  2. * Copyright (c) 2014, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. */
  10. #import "OSSTask.h"
  11. #import "OSSLog.h"
  12. #import "OSSConstants.h"
  13. #import "OSSDefine.h"
  14. #import <libkern/OSAtomic.h>
  15. #import "OSSBolts.h"
  16. NS_ASSUME_NONNULL_BEGIN
  17. __attribute__ ((noinline)) void ossWarnBlockingOperationOnMainThread() {
  18. NSLog(@"Warning: A long-running operation is being executed on the main thread. \n"
  19. " Break on warnBlockingOperationOnMainThread() to debug.");
  20. }
  21. NSString *const OSSTaskErrorDomain = @"bolts";
  22. NSInteger const kOSSMultipleErrorsError = 80175001;
  23. NSString *const OSSTaskMultipleExceptionsException = @"OSSMultipleExceptionsException";
  24. NSString *const OSSTaskMultipleErrorsUserInfoKey = @"errors";
  25. NSString *const OSSTaskMultipleExceptionsUserInfoKey = @"exceptions";
  26. @interface OSSTask () {
  27. id _result;
  28. NSError *_error;
  29. NSException *_exception;
  30. }
  31. @property (nonatomic, assign, readwrite, getter=isCancelled) BOOL cancelled;
  32. @property (nonatomic, assign, readwrite, getter=isFaulted) BOOL faulted;
  33. @property (nonatomic, assign, readwrite, getter=isCompleted) BOOL completed;
  34. @property (nonatomic, strong) NSObject *lock;
  35. @property (nonatomic, strong) NSCondition *condition;
  36. @property (nonatomic, strong) NSMutableArray *callbacks;
  37. @end
  38. @implementation OSSTask
  39. #pragma mark - Initializer
  40. - (instancetype)init {
  41. self = [super init];
  42. if (!self) return self;
  43. _lock = [[NSObject alloc] init];
  44. _condition = [[NSCondition alloc] init];
  45. _callbacks = [NSMutableArray array];
  46. return self;
  47. }
  48. - (instancetype)initWithResult:(_Nullable id)result {
  49. self = [super init];
  50. if (self) {
  51. [self trySetResult:result];
  52. }
  53. return self;
  54. }
  55. - (instancetype)initWithError:(NSError *)error {
  56. self = [super init];
  57. if (!self) return self;
  58. [self trySetError:error];
  59. return self;
  60. }
  61. - (instancetype)initWithException:(NSException *)exception {
  62. self = [super init];
  63. if (!self) return self;
  64. [self trySetException:exception];
  65. return self;
  66. }
  67. - (instancetype)initCancelled {
  68. self = [super init];
  69. if (!self) return self;
  70. [self trySetCancelled];
  71. return self;
  72. }
  73. #pragma mark - Task Class methods
  74. + (instancetype)taskWithResult:(_Nullable id)result {
  75. return [[self alloc] initWithResult:result];
  76. }
  77. + (instancetype)taskWithError:(NSError *)error {
  78. return [[self alloc] initWithError:error];
  79. }
  80. + (instancetype)taskWithException:(NSException *)exception {
  81. return [[self alloc] initWithException:exception];
  82. }
  83. + (instancetype)cancelledTask {
  84. return [[self alloc] initCancelled];
  85. }
  86. + (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<OSSTask *> *)tasks {
  87. __block int32_t total = (int32_t)tasks.count;
  88. if (total == 0) {
  89. return [self taskWithResult:nil];
  90. }
  91. __block int32_t cancelled = 0;
  92. NSObject *lock = [[NSObject alloc] init];
  93. NSMutableArray *errors = [NSMutableArray array];
  94. NSMutableArray *exceptions = [NSMutableArray array];
  95. OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
  96. for (OSSTask *task in tasks) {
  97. [task continueWithBlock:^id(OSSTask *task) {
  98. if (task.exception) {
  99. @synchronized (lock) {
  100. [exceptions addObject:task.exception];
  101. }
  102. } else if (task.error) {
  103. @synchronized (lock) {
  104. [errors addObject:task.error];
  105. }
  106. } else if (task.cancelled) {
  107. OSAtomicIncrement32Barrier(&cancelled);
  108. }
  109. if (OSAtomicDecrement32Barrier(&total) == 0) {
  110. if (exceptions.count > 0) {
  111. if (exceptions.count == 1) {
  112. tcs.exception = [exceptions firstObject];
  113. } else {
  114. NSException *exception =
  115. [NSException exceptionWithName:OSSTaskMultipleExceptionsException
  116. reason:@"There were multiple exceptions."
  117. userInfo:@{ OSSTaskMultipleExceptionsUserInfoKey: exceptions }];
  118. tcs.exception = exception;
  119. }
  120. } else if (errors.count > 0) {
  121. if (errors.count == 1) {
  122. tcs.error = [errors firstObject];
  123. } else {
  124. NSError *error = [NSError errorWithDomain:OSSTaskErrorDomain
  125. code:kOSSMultipleErrorsError
  126. userInfo:@{ OSSTaskMultipleErrorsUserInfoKey: errors }];
  127. tcs.error = error;
  128. }
  129. } else if (cancelled > 0) {
  130. [tcs cancel];
  131. } else {
  132. tcs.result = nil;
  133. }
  134. }
  135. return nil;
  136. }];
  137. }
  138. return tcs.task;
  139. }
  140. + (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray<OSSTask *> *)tasks {
  141. return [[self taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(OSSTask *task) {
  142. return [tasks valueForKey:@"result"];
  143. }];
  144. }
  145. + (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<OSSTask *> *)tasks
  146. {
  147. __block int32_t total = (int32_t)tasks.count;
  148. if (total == 0) {
  149. return [self taskWithResult:nil];
  150. }
  151. __block int completed = 0;
  152. __block int32_t cancelled = 0;
  153. NSObject *lock = [NSObject new];
  154. NSMutableArray<NSError *> *errors = [NSMutableArray new];
  155. NSMutableArray<NSException *> *exceptions = [NSMutableArray new];
  156. OSSTaskCompletionSource *source = [OSSTaskCompletionSource taskCompletionSource];
  157. for (OSSTask *task in tasks) {
  158. [task continueWithBlock:^id(OSSTask *task) {
  159. if (task.exception != nil) {
  160. @synchronized(lock) {
  161. [exceptions addObject:task.exception];
  162. }
  163. } else if (task.error != nil) {
  164. @synchronized(lock) {
  165. [errors addObject:task.error];
  166. }
  167. } else if (task.cancelled) {
  168. OSAtomicIncrement32Barrier(&cancelled);
  169. } else {
  170. if(OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
  171. [source setResult:task.result];
  172. }
  173. }
  174. if (OSAtomicDecrement32Barrier(&total) == 0 &&
  175. OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
  176. if (cancelled > 0) {
  177. [source cancel];
  178. } else if (exceptions.count > 0) {
  179. if (exceptions.count == 1) {
  180. source.exception = exceptions.firstObject;
  181. } else {
  182. NSException *exception =
  183. [NSException exceptionWithName:OSSTaskMultipleExceptionsException
  184. reason:@"There were multiple exceptions."
  185. userInfo:@{ @"exceptions": exceptions }];
  186. source.exception = exception;
  187. }
  188. } else if (errors.count > 0) {
  189. if (errors.count == 1) {
  190. source.error = errors.firstObject;
  191. } else {
  192. NSError *error = [NSError errorWithDomain:OSSTaskErrorDomain
  193. code:kOSSMultipleErrorsError
  194. userInfo:@{ @"errors": errors }];
  195. source.error = error;
  196. }
  197. }
  198. }
  199. // Abort execution of per tasks continuations
  200. return nil;
  201. }];
  202. }
  203. return source.task;
  204. }
  205. + (instancetype)taskWithDelay:(int)millis {
  206. OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
  207. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
  208. dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
  209. tcs.result = nil;
  210. });
  211. return tcs.task;
  212. }
  213. + (instancetype)taskWithDelay:(int)millis cancellationToken:(nullable OSSCancellationToken *)token {
  214. if (token.cancellationRequested) {
  215. return [OSSTask cancelledTask];
  216. }
  217. OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
  218. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
  219. dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
  220. if (token.cancellationRequested) {
  221. [tcs cancel];
  222. return;
  223. }
  224. tcs.result = nil;
  225. });
  226. return tcs.task;
  227. }
  228. + (instancetype)taskFromExecutor:(OSSExecutor *)executor withBlock:(nullable id (^)(void))block {
  229. return [[self taskWithResult:nil] continueWithExecutor:executor withBlock:^id(OSSTask *task) {
  230. return block();
  231. }];
  232. }
  233. #pragma mark - Custom Setters/Getters
  234. - (nullable id)result {
  235. @synchronized(self.lock) {
  236. return _result;
  237. }
  238. }
  239. - (BOOL)trySetResult:(nullable id)result {
  240. @synchronized(self.lock) {
  241. if (self.completed) {
  242. return NO;
  243. }
  244. self.completed = YES;
  245. _result = result;
  246. [self runContinuations];
  247. return YES;
  248. }
  249. }
  250. - (nullable NSError *)error {
  251. @synchronized(self.lock) {
  252. return _error;
  253. }
  254. }
  255. - (BOOL)trySetError:(NSError *)error {
  256. @synchronized(self.lock) {
  257. if (self.completed) {
  258. return NO;
  259. }
  260. self.completed = YES;
  261. self.faulted = YES;
  262. _error = error;
  263. [self runContinuations];
  264. return YES;
  265. }
  266. }
  267. - (nullable NSException *)exception {
  268. @synchronized(self.lock) {
  269. return _exception;
  270. }
  271. }
  272. - (BOOL)trySetException:(NSException *)exception {
  273. @synchronized(self.lock) {
  274. if (self.completed) {
  275. return NO;
  276. }
  277. self.completed = YES;
  278. self.faulted = YES;
  279. _exception = exception;
  280. [self runContinuations];
  281. return YES;
  282. }
  283. }
  284. - (BOOL)isCancelled {
  285. @synchronized(self.lock) {
  286. return _cancelled;
  287. }
  288. }
  289. - (BOOL)isFaulted {
  290. @synchronized(self.lock) {
  291. return _faulted;
  292. }
  293. }
  294. - (BOOL)trySetCancelled {
  295. @synchronized(self.lock) {
  296. if (self.completed) {
  297. return NO;
  298. }
  299. self.completed = YES;
  300. self.cancelled = YES;
  301. [self runContinuations];
  302. return YES;
  303. }
  304. }
  305. - (BOOL)isCompleted {
  306. @synchronized(self.lock) {
  307. return _completed;
  308. }
  309. }
  310. - (void)runContinuations {
  311. @synchronized(self.lock) {
  312. [self.condition lock];
  313. [self.condition broadcast];
  314. [self.condition unlock];
  315. for (void (^callback)(void) in self.callbacks) {
  316. callback();
  317. }
  318. [self.callbacks removeAllObjects];
  319. }
  320. }
  321. #pragma mark - Chaining methods
  322. - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor withBlock:(OSSContinuationBlock)block {
  323. return [self continueWithExecutor:executor block:block cancellationToken:nil];
  324. }
  325. - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
  326. block:(OSSContinuationBlock)block
  327. cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
  328. OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
  329. // Capture all of the state that needs to used when the continuation is complete.
  330. dispatch_block_t executionBlock = ^{
  331. if (cancellationToken.cancellationRequested) {
  332. [tcs cancel];
  333. return;
  334. }
  335. id result = nil;
  336. @try {
  337. result = block(self);
  338. } @catch (NSException *exception) {
  339. NSError *error = [NSError errorWithDomain:OSSClientErrorDomain
  340. code:OSSClientErrorCodeExcpetionCatched
  341. userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", exception]}];
  342. tcs.error = error;
  343. OSSLogError(@"exception name: %@",[exception name]);
  344. OSSLogError(@"exception reason: %@",[exception reason]);
  345. return;
  346. }
  347. if ([result isKindOfClass:[OSSTask class]]) {
  348. id (^setupWithTask) (OSSTask *) = ^id(OSSTask *task) {
  349. if (cancellationToken.cancellationRequested || task.cancelled) {
  350. [tcs cancel];
  351. } else if (task.exception) {
  352. NSError *error = [NSError errorWithDomain:OSSClientErrorDomain
  353. code:OSSClientErrorCodeExcpetionCatched
  354. userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", task.exception]}];
  355. tcs.error = error;
  356. } else if (task.error) {
  357. tcs.error = task.error;
  358. } else {
  359. tcs.result = task.result;
  360. }
  361. return nil;
  362. };
  363. OSSTask *resultTask = (OSSTask *)result;
  364. if (resultTask.completed) {
  365. setupWithTask(resultTask);
  366. } else {
  367. [resultTask continueWithBlock:setupWithTask];
  368. }
  369. } else {
  370. tcs.result = result;
  371. }
  372. };
  373. BOOL completed;
  374. @synchronized(self.lock) {
  375. completed = self.completed;
  376. if (!completed) {
  377. [self.callbacks addObject:[^{
  378. [executor execute:executionBlock];
  379. } copy]];
  380. }
  381. }
  382. if (completed) {
  383. [executor execute:executionBlock];
  384. }
  385. return tcs.task;
  386. }
  387. - (OSSTask *)continueWithBlock:(OSSContinuationBlock)block {
  388. return [self continueWithExecutor:[OSSExecutor defaultExecutor] block:block cancellationToken:nil];
  389. }
  390. - (OSSTask *)continueWithBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
  391. return [self continueWithExecutor:[OSSExecutor defaultExecutor] block:block cancellationToken:cancellationToken];
  392. }
  393. - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
  394. withSuccessBlock:(OSSContinuationBlock)block {
  395. return [self continueWithExecutor:executor successBlock:block cancellationToken:nil];
  396. }
  397. - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
  398. successBlock:(OSSContinuationBlock)block
  399. cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
  400. if (cancellationToken.cancellationRequested) {
  401. return [OSSTask cancelledTask];
  402. }
  403. return [self continueWithExecutor:executor block:^id(OSSTask *task) {
  404. if (task.faulted || task.cancelled) {
  405. return task;
  406. } else {
  407. return block(task);
  408. }
  409. } cancellationToken:cancellationToken];
  410. }
  411. - (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block {
  412. return [self continueWithExecutor:[OSSExecutor defaultExecutor] successBlock:block cancellationToken:nil];
  413. }
  414. - (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
  415. return [self continueWithExecutor:[OSSExecutor defaultExecutor] successBlock:block cancellationToken:cancellationToken];
  416. }
  417. #pragma mark - Syncing Task (Avoid it)
  418. - (void)warnOperationOnMainThread {
  419. ossWarnBlockingOperationOnMainThread();
  420. }
  421. - (void)waitUntilFinished {
  422. if ([NSThread isMainThread]) {
  423. [self warnOperationOnMainThread];
  424. }
  425. @synchronized(self.lock) {
  426. if (self.completed) {
  427. return;
  428. }
  429. [self.condition lock];
  430. }
  431. while (!self.completed) {
  432. [self.condition wait];
  433. }
  434. [self.condition unlock];
  435. }
  436. #pragma mark - NSObject
  437. - (NSString *)description {
  438. // Acquire the data from the locked properties
  439. BOOL completed;
  440. BOOL cancelled;
  441. BOOL faulted;
  442. NSString *resultDescription = nil;
  443. @synchronized(self.lock) {
  444. completed = self.completed;
  445. cancelled = self.cancelled;
  446. faulted = self.faulted;
  447. resultDescription = completed ? [NSString stringWithFormat:@" result = %@", self.result] : @"";
  448. }
  449. // Description string includes status information and, if available, the
  450. // result since in some ways this is what a promise actually "is".
  451. return [NSString stringWithFormat:@"<%@: %p; completed = %@; cancelled = %@; faulted = %@;%@>",
  452. NSStringFromClass([self class]),
  453. self,
  454. completed ? @"YES" : @"NO",
  455. cancelled ? @"YES" : @"NO",
  456. faulted ? @"YES" : @"NO",
  457. resultDescription];
  458. }
  459. @end
  460. @implementation OSSTask(OSS)
  461. - (BOOL)isSuccessful {
  462. if (self.cancelled || self.faulted) {
  463. return false;
  464. }
  465. return true;
  466. }
  467. - (NSError *)toError {
  468. if (self.cancelled) {
  469. return [NSError errorWithDomain:OSSClientErrorDomain
  470. code:OSSClientErrorCodeTaskCancelled
  471. userInfo:@{OSSErrorMessageTOKEN: @"This task is cancelled"}];
  472. } else if (self.error) {
  473. return self.error;
  474. } else if (self.exception) {
  475. return [NSError errorWithDomain:OSSClientErrorDomain
  476. code:OSSClientErrorCodeExcpetionCatched
  477. userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", self.exception]}];
  478. }
  479. return nil;
  480. }
  481. - (OSSTask *)completed:(OSSCompleteBlock)block {
  482. return [self continueWithBlock:^id _Nullable(OSSTask * _Nonnull task) {
  483. if ([task isSuccessful]) {
  484. block(YES, nil, task.result);
  485. } else {
  486. block(NO, [task toError], nil);
  487. }
  488. return nil;
  489. }];
  490. }
  491. @end
  492. NS_ASSUME_NONNULL_END