Perform Engine Microtasks in JSIExecutor

Summary:
Changelog: [Internal]

This diff introduce a helper `performMicrotaskCheckpoint` to
repetitively invoke `jsi::Runtime::drainMicrotasks` to exhaust
the microtasks queue provided by JS VMs. Please refer to `jsi.h`
for more details about the behavior of the API.

Conceptually, the checkpoint needs to be performed whenever the
JS stack is considered empty. In practice, we can just make a
call whenever RN is returned from C++->JS calls.

In the current RN, this happened in JSIExecutor:
- `::callFunction` => `callFunctionReturnFlushedQueue_-> call`
- `::invokeCallback` => `invokeCallbackAndReturnFlushedQueue_-> call`
- `::flush` => `flushedQueue_->call`

Each of them invoke a bound method on the JS bridge object. Note
that `setImmediate` callbacks are executed before they returned.
This means immmediates are invoked before engine microtasks. This
is okay because the priority between `setImmediate` and engine
"microtask" are not defined. (`setImmediate` is non-standard and RN
already treat `setImmediate` in a similar priority as microtask
for the existing Promise polyfill.)

Reviewed By: RSNara

Differential Revision: D27729702

fbshipit-source-id: b64b3705d2ff5100075d860c89f03a847369b7ac
This commit is contained in:
Xuan Huang 2021-04-23 02:41:41 -07:00 коммит произвёл Facebook GitHub Bot
Родитель bddff73790
Коммит 7310847758
1 изменённых файлов: 35 добавлений и 2 удалений

Просмотреть файл

@ -7,6 +7,7 @@
#include "jsireact/JSIExecutor.h"
#include <cxxreact/ErrorUtils.h>
#include <cxxreact/JSBigString.h>
#include <cxxreact/ModuleRegistry.h>
#include <cxxreact/ReactMarker.h>
@ -204,6 +205,30 @@ void JSIExecutor::registerBundle(
ReactMarker::REGISTER_JS_SEGMENT_STOP, tag.c_str());
}
// Looping on \c drainMicrotasks until it completes or hits the retries bound.
static void performMicrotaskCheckpoint(jsi::Runtime &runtime) {
uint8_t retries = 0;
// A heuristic number to guard inifinite or absurd numbers of retries.
const static unsigned int kRetriesBound = 255;
while (retries < kRetriesBound) {
try {
// The default behavior of \c drainMicrotasks is unbounded execution.
// We may want to make it bounded in the future.
if (runtime.drainMicrotasks()) {
break;
}
} catch (jsi::JSError &error) {
handleJSError(runtime, error, true);
}
retries++;
}
if (retries == kRetriesBound) {
throw std::runtime_error("Hits microtasks retries bound.");
}
}
void JSIExecutor::callFunction(
const std::string &moduleId,
const std::string &methodId,
@ -240,6 +265,8 @@ void JSIExecutor::callFunction(
std::runtime_error("Error calling " + moduleId + "." + methodId));
}
performMicrotaskCheckpoint(*runtime_);
callNativeModules(ret, true);
}
@ -259,6 +286,8 @@ void JSIExecutor::invokeCallback(
folly::to<std::string>("Error invoking callback ", callbackId)));
}
performMicrotaskCheckpoint(*runtime_);
callNativeModules(ret, true);
}
@ -394,7 +423,9 @@ void JSIExecutor::callNativeModules(const Value &queue, bool isEndOfBatch) {
void JSIExecutor::flush() {
SystraceSection s("JSIExecutor::flush");
if (flushedQueue_) {
callNativeModules(flushedQueue_->call(*runtime_), true);
Value ret = flushedQueue_->call(*runtime_);
performMicrotaskCheckpoint(*runtime_);
callNativeModules(ret, true);
return;
}
@ -410,7 +441,9 @@ void JSIExecutor::flush() {
// If calls were made, we bind to the JS bridge methods, and use them to
// get the pending queue of native calls.
bindBridge();
callNativeModules(flushedQueue_->call(*runtime_), true);
Value ret = flushedQueue_->call(*runtime_);
performMicrotaskCheckpoint(*runtime_);
callNativeModules(ret, true);
} else if (delegate_) {
// If we have a delegate, we need to call it; we pass a null list to
// callNativeModules, since we know there are no native calls, without