Skip to content

Commit

Permalink
Introducing PseudoUIManagerQueue
Browse files Browse the repository at this point in the history
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
  • Loading branch information
shergin authored and facebook-github-bot committed Oct 9, 2017
1 parent 1c24440 commit 5e25c0e
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 5 deletions.
54 changes: 53 additions & 1 deletion React/Modules/RCTUIManagerUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,51 @@
#import <React/RCTAssert.h>
#import <React/RCTDefines.h>

/**
* Queues Problem Intro:
* UIManager queue is a special queue because it has a 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 the 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 that 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.
*/

/**
* Returns UIManager queue.
*/
Expand All @@ -24,9 +69,16 @@ RCT_EXTERN char *const RCTUIManagerQueueName;

/**
* Check if we are currently on UIManager queue.
* Please do not use this unless you really know what you're doing.
*/
RCT_EXTERN BOOL RCTIsUIManagerQueue(void);

/**
* Check if we are currently on Pseudo UIManager queue.
* Please do not use this unless you really know what you're doing.
*/
RCT_EXTERN BOOL RCTIsPseudoUIManagerQueue(void);

/**
* *Asynchronously* executes the specified block on the UIManager queue.
* Unlike `dispatch_async()` this will execute the block immediately
Expand All @@ -45,5 +97,5 @@ RCT_EXTERN void RCTUnsafeExecuteOnUIManagerQueueSync(dispatch_block_t block);
/**
* Convenience macro for asserting that we're running on UIManager queue.
*/
#define RCTAssertUIManagerQueue() RCTAssert(RCTIsUIManagerQueue(), \
#define RCTAssertUIManagerQueue() RCTAssert(RCTIsUIManagerQueue() || RCTIsPseudoUIManagerQueue(), \
@"This function must be called on the UIManager queue")
41 changes: 37 additions & 4 deletions React/Modules/RCTUIManagerUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

char *const RCTUIManagerQueueName = "com.facebook.react.ShadowQueue";

static BOOL pseudoUIManagerQueueFlag = NO;

dispatch_queue_t RCTGetUIManagerQueue(void)
{
static dispatch_queue_t shadowQueue;
Expand All @@ -39,9 +41,18 @@ BOOL RCTIsUIManagerQueue()
return dispatch_get_specific(queueKey) == queueKey;
}

BOOL RCTIsPseudoUIManagerQueue()
{
if (RCTIsMainQueue()) {
return pseudoUIManagerQueueFlag;
}

return NO;
}

void RCTExecuteOnUIManagerQueue(dispatch_block_t block)
{
if (RCTIsUIManagerQueue()) {
if (RCTIsUIManagerQueue() || RCTIsPseudoUIManagerQueue()) {
block();
} else {
dispatch_async(RCTGetUIManagerQueue(), ^{
Expand All @@ -52,11 +63,33 @@ void RCTExecuteOnUIManagerQueue(dispatch_block_t block)

void RCTUnsafeExecuteOnUIManagerQueueSync(dispatch_block_t block)
{
if (RCTIsUIManagerQueue()) {
if (RCTIsUIManagerQueue() || RCTIsPseudoUIManagerQueue()) {
block();
} else {
dispatch_sync(RCTGetUIManagerQueue(), ^{
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();
});
pseudoUIManagerQueueFlag = NO;
// Signalling UIManager block.
dispatch_semaphore_signal(uiManagerQueueBlockingSemaphore);
} else {
dispatch_sync(RCTGetUIManagerQueue(), ^{
block();
});
}
}
}

0 comments on commit 5e25c0e

Please sign in to comment.