react-native/React/Modules/RCTUIManagerUtils.m

105 lines
3.2 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTUIManagerUtils.h"
#import <libkern/OSAtomic.h>
#import "RCTAssert.h"
char *const RCTUIManagerQueueName = "com.facebook.react.ShadowQueue";
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
static BOOL pseudoUIManagerQueueFlag = NO;
dispatch_queue_t RCTGetUIManagerQueue(void)
{
static dispatch_queue_t shadowQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, attr);
} else {
shadowQueue = dispatch_queue_create(RCTUIManagerQueueName, DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
}
});
return shadowQueue;
}
BOOL RCTIsUIManagerQueue()
{
static void *queueKey = &queueKey;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_queue_set_specific(RCTGetUIManagerQueue(), queueKey, queueKey, NULL);
});
return dispatch_get_specific(queueKey) == queueKey;
}
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
BOOL RCTIsPseudoUIManagerQueue()
{
if (RCTIsMainQueue()) {
return pseudoUIManagerQueueFlag;
}
return NO;
}
void RCTExecuteOnUIManagerQueue(dispatch_block_t block)
{
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
if (RCTIsUIManagerQueue() || RCTIsPseudoUIManagerQueue()) {
block();
} else {
dispatch_async(RCTGetUIManagerQueue(), ^{
block();
});
}
}
void RCTUnsafeExecuteOnUIManagerQueueSync(dispatch_block_t block)
{
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
if (RCTIsUIManagerQueue() || RCTIsPseudoUIManagerQueue()) {
block();
} else {
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
if (RCTIsMainQueue()) {
dispatch_semaphore_t mainQueueBlockingSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t uiManagerQueueBlockingSemaphore = dispatch_semaphore_create(0);
// Dispatching block which blocks UI Manager queue.
dispatch_async(RCTGetUIManagerQueue(), ^{
// Initiating `block` execution on main queue.
dispatch_semaphore_signal(mainQueueBlockingSemaphore);
// Waiting for finishing `block`.
dispatch_semaphore_wait(uiManagerQueueBlockingSemaphore, DISPATCH_TIME_FOREVER);
});
// Waiting for block on UIManager queue.
dispatch_semaphore_wait(mainQueueBlockingSemaphore, DISPATCH_TIME_FOREVER);
pseudoUIManagerQueueFlag = YES;
// `block` execution while UIManager queue is blocked by semaphore.
block();
Introducing PseudoUIManagerQueue Summary: Queues Problem Intro: UIManager queue is special queue because it has special relationship with the Main queue. This particular relationship comes from two key factors: 1. UIManager initiates execution of many blocks on the Main queue; 2. In some cases, we want to initiate (and wait for) some UIManager's work *synchronously* from the Main queue. So, how can we meet these criteria? "Pseudo UIManager queue" comes to rescue! "Pseudo UIManager queue" means safe execution of typical UIManager's work on the Main queue while the UIManager queue is explicitly blocked for preventing simultaneous/concurrent memory access. So, how can we technically do this? 1. `RCTAssertUIManagerQueue` is okay with execution on both actual UIManager and Pseudo UIManager queues. 2. Both `RCTExecuteOnUIManagerQueue` and `RCTUnsafeExecuteOnUIManagerQueueSync` execute given block *synchronously* if they were called on actual UIManager or Pseudo UIManager queues. 3. `RCTExecuteOnMainQueue` executes given block *synchronously* if we already on the Main queue. 4. `RCTUnsafeExecuteOnUIManagerQueueSync` is smart enough to do the trick: It detects calling on the Main queue and in this case, instead of doing trivial *synchronous* dispatch, it does: - Block the Main queue; - Dispatch the special block on UIManager queue to block the queue and concurrent memory access; - Execute the given block on the Main queue; - Unblock the UIManager queue. Imagine the analogy: We have two queues: the Main one and UIManager one. And these queues are two lanes of railway go in parallel. Then, at some point, we merge UIManager lane with the Main lane, and all cars use the unified the Main lane. And then we split lanes again. This solution assumes that the code running on UIManager queue will never *explicitly* block the Main queue via calling `RCTUnsafeExecuteOnMainQueueSync`. Otherwise, it can cause a deadlock. Reviewed By: mmmulani Differential Revision: D5935464 fbshipit-source-id: 6a60ff236280d825b4e2b101f06222266097b97f
2017-10-09 04:23:52 +00:00
pseudoUIManagerQueueFlag = NO;
// Signalling UIManager block.
dispatch_semaphore_signal(uiManagerQueueBlockingSemaphore);
} else {
dispatch_sync(RCTGetUIManagerQueue(), ^{
block();
});
}
}
}
NSNumber *RCTAllocateRootViewTag()
{
// Numbering of these tags goes from 1, 11, 21, 31, ..., 100501, ...
static int64_t rootViewTagCounter = -1;
return @(OSAtomicIncrement64(&rootViewTagCounter) * 10 + 1);
}