|
5 | 5 | * LICENSE file in the root directory of this source tree.
|
6 | 6 | */
|
7 | 7 |
|
| 8 | +#import <Foundation/Foundation.h> |
| 9 | + |
8 | 10 | #import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
|
9 |
| -#include <mutex> |
10 |
| -#include <thread> |
| 11 | +#import <react/debug/react_native_assert.h> |
| 12 | +#import <react/utils/OnScopeExit.h> |
| 13 | +#import <functional> |
| 14 | +#import <mutex> |
| 15 | + |
| 16 | +namespace { |
| 17 | +struct UITask { |
| 18 | + std::promise<void> _isDone; |
| 19 | + std::mutex _mutex; |
| 20 | + std::function<void()> _uiWork; |
| 21 | + std::atomic_bool _hasStarted; |
| 22 | + |
| 23 | + public: |
| 24 | + UITask(std::function<void()> uiWork) : _uiWork(uiWork), _hasStarted(false) {} |
| 25 | + |
| 26 | + void run() |
| 27 | + { |
| 28 | + bool expected = false; |
| 29 | + if (!_hasStarted.compare_exchange_strong(expected, true)) { |
| 30 | + return; |
| 31 | + } |
| 32 | + facebook::react::OnScopeExit onScopeExit(^{ |
| 33 | + _uiWork = nil; |
| 34 | + _isDone.set_value(); |
| 35 | + }); |
| 36 | + _uiWork(); |
| 37 | + } |
| 38 | + |
| 39 | + bool hasStarted() |
| 40 | + { |
| 41 | + return _hasStarted.load(); |
| 42 | + } |
| 43 | + |
| 44 | + std::future<void> getFuture() |
| 45 | + { |
| 46 | + return _isDone.get_future(); |
| 47 | + } |
| 48 | +}; |
| 49 | + |
| 50 | +static std::mutex _mutex; |
| 51 | +static std::condition_variable _cv; |
| 52 | + |
| 53 | +// Global state |
| 54 | +static bool _isRunningPendingUITask = false; |
| 55 | +static std::shared_ptr<UITask> _pendingUITask; |
| 56 | + |
| 57 | +void runPendingUITask() |
| 58 | +{ |
| 59 | + facebook::react::OnScopeExit onScopeExit([&]() { |
| 60 | + _pendingUITask = nullptr; |
| 61 | + _isRunningPendingUITask = false; |
| 62 | + }); |
| 63 | + _isRunningPendingUITask = true; |
| 64 | + _pendingUITask->run(); |
| 65 | +} |
| 66 | +} // namespace |
11 | 67 |
|
12 | 68 | namespace facebook::react {
|
13 |
| -/** |
14 |
| - * Example order of events (when not a sync call in runtimeExecutor |
15 |
| - * jsWork): |
16 |
| - * - [UI thread] Lock all mutexes at start |
17 |
| - * - [UI thread] Schedule "runtime capture block" on js thread |
18 |
| - * - [UI thread] Wait for runtime capture: runtimeCaptured.lock() |
19 |
| - * - [JS thread] Capture runtime by setting runtimePtr |
20 |
| - * - [JS thread] Signal runtime captured: runtimeCaptured.unlock() |
21 |
| - * - [UI thread] Call jsWork using runtimePtr |
22 |
| - * - [JS thread] Wait until jsWork done: jsWorkDone.lock() |
23 |
| - * - [UI thread] Signal jsWork done: jsWorkDone.unlock() |
24 |
| - * - [UI thread] Wait until runtime capture block finished: |
25 |
| - * runtimeCaptureBlockDone.lock() |
26 |
| - * - [JS thread] Signal runtime capture block is finished: |
27 |
| - * runtimeCaptureBlockDone.unlock() |
28 |
| - */ |
29 | 69 | void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
|
30 | 70 | const RuntimeExecutor &runtimeExecutor,
|
31 | 71 | std::function<void(jsi::Runtime &runtime)> &&jsWork) noexcept
|
32 | 72 | {
|
33 |
| - // Note: We need the third mutex to get back to the main thread before |
34 |
| - // the lambda is finished (because all mutexes are allocated on the stack). |
| 73 | + react_native_assert([[NSThread currentThread] isMainThread] && !_isRunningPendingUITask); |
35 | 74 |
|
36 |
| - std::mutex runtimeCaptured; |
| 75 | + jsi::Runtime *runtime = nullptr; |
37 | 76 | std::mutex jsWorkDone;
|
38 |
| - std::mutex runtimeCaptureBlockDone; |
39 |
| - |
40 |
| - runtimeCaptured.lock(); |
41 | 77 | jsWorkDone.lock();
|
42 |
| - runtimeCaptureBlockDone.lock(); |
43 | 78 |
|
44 |
| - jsi::Runtime *runtimePtr; |
| 79 | + { |
| 80 | + std::unique_lock<std::mutex> lock(_mutex); |
| 81 | + if (_pendingUITask) { |
| 82 | + runPendingUITask(); |
| 83 | + } |
| 84 | + |
| 85 | + runtimeExecutor([&](jsi::Runtime &rt) { |
| 86 | + { |
| 87 | + std::lock_guard<std::mutex> lock(_mutex); |
| 88 | + runtime = &rt; |
| 89 | + _cv.notify_one(); |
| 90 | + } |
45 | 91 |
|
46 |
| - auto threadId = std::this_thread::get_id(); |
47 |
| - auto runtimeCaptureBlock = [&](jsi::Runtime &runtime) { |
48 |
| - runtimePtr = &runtime; |
| 92 | + // Block the js thread until jsWork finishes on calling thread |
| 93 | + jsWorkDone.lock(); |
| 94 | + }); |
49 | 95 |
|
50 |
| - if (threadId == std::this_thread::get_id()) { |
51 |
| - // In case of a synchronous call, we should unlock mutexes and return. |
52 |
| - runtimeCaptured.unlock(); |
53 |
| - runtimeCaptureBlockDone.unlock(); |
54 |
| - return; |
55 |
| - } |
| 96 | + while (true) { |
| 97 | + _cv.wait(lock, [&] { return runtime != nullptr || _pendingUITask != nullptr; }); |
56 | 98 |
|
57 |
| - runtimeCaptured.unlock(); |
58 |
| - // `jsWork` is called somewhere here. |
59 |
| - jsWorkDone.lock(); |
60 |
| - runtimeCaptureBlockDone.unlock(); |
61 |
| - }; |
62 |
| - runtimeExecutor(std::move(runtimeCaptureBlock)); |
| 99 | + if (_pendingUITask != nullptr) { |
| 100 | + runPendingUITask(); |
| 101 | + } else { |
| 102 | + break; |
| 103 | + } |
| 104 | + } |
| 105 | + } |
63 | 106 |
|
64 |
| - runtimeCaptured.lock(); |
65 |
| - jsWork(*runtimePtr); |
| 107 | + jsWork(*runtime); |
66 | 108 | jsWorkDone.unlock();
|
67 |
| - runtimeCaptureBlockDone.lock(); |
| 109 | +} |
| 110 | + |
| 111 | +std::future<void> schedulePotentiallyDeadlockingUITask(std::function<void()> work) |
| 112 | +{ |
| 113 | + std::lock_guard<std::mutex> lock(_mutex); |
| 114 | + react_native_assert((!_pendingUITask || _pendingUITask->hasStarted())); |
| 115 | + |
| 116 | + auto uiTask = std::make_shared<UITask>(work); |
| 117 | + dispatch_async(dispatch_get_main_queue(), ^{ |
| 118 | + uiTask->run(); |
| 119 | + }); |
| 120 | + |
| 121 | + _pendingUITask = uiTask; |
| 122 | + _cv.notify_one(); |
| 123 | + return uiTask->getFuture(); |
68 | 124 | }
|
69 | 125 |
|
70 | 126 | } // namespace facebook::react
|
0 commit comments