Skip to content

Commit bd89afa

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement queueMicrotask and drainMicrotasks in JSC
Summary: Changelog: [internal] ## Context We want to enable the new React Native event loop by default for all users on the new RN architecture (on the bridgeless initialization path more concretely), which requires support for microtasks in all the JS engines that the support (Hermes already has it, JSC doesn't). ## Changes This adds initial support for microtasks in JSC, so we can schedule and execute microtasks in this runtime. One limitation about this approach is that, AFAIK, the public API for JSC doesn't allow us to customize its internal microtask queue or specify the method to be used by its built-in `Promise` or native `async function`, so we're forced to continue using a polyfill in that case (which uses `setImmediate` that will be mapped to `queueMicrotask`). Reviewed By: NickGerleman Differential Revision: D54302534 fbshipit-source-id: 47f71620344a81bc6624917f77452106ffbf55a3
1 parent 036f47e commit bd89afa

File tree

1 file changed

+30
-2
lines changed

1 file changed

+30
-2
lines changed

packages/react-native/ReactCommon/jsc/JSCRuntime.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
#include <atomic>
1414
#include <condition_variable>
1515
#include <cstdlib>
16+
#include <deque>
1617
#include <mutex>
17-
#include <queue>
1818
#include <sstream>
1919
#include <thread>
2020

@@ -51,6 +51,12 @@ class JSCRuntime : public jsi::Runtime {
5151
const std::shared_ptr<const jsi::Buffer>& buffer,
5252
const std::string& sourceURL) override;
5353

54+
// If we use this interface to implement microtasks in the host we need to
55+
// polyfill `Promise` to use these methods, because JSC doesn't currently
56+
// support providing a custom queue for its built-in implementation.
57+
// Not doing this would result in a non-compliant behavior, as microtasks
58+
// wouldn't execute in the order in which they were queued.
59+
void queueMicrotask(const jsi::Function& callback) override;
5460
bool drainMicrotasks(int maxMicrotasksHint = -1) override;
5561

5662
jsi::Object global() override;
@@ -265,6 +271,7 @@ class JSCRuntime : public jsi::Runtime {
265271
std::atomic<bool> ctxInvalid_;
266272
std::string desc_;
267273
JSValueRef nativeStateSymbol_ = nullptr;
274+
std::deque<jsi::Function> microtaskQueue_;
268275
#ifndef NDEBUG
269276
mutable std::atomic<intptr_t> objectCounter_;
270277
mutable std::atomic<intptr_t> symbolCounter_;
@@ -378,6 +385,10 @@ JSCRuntime::JSCRuntime(JSGlobalContextRef ctx)
378385
}
379386

380387
JSCRuntime::~JSCRuntime() {
388+
// We need to clear the microtask queue to remove all references to the
389+
// callbacks, so objectCounter_ would be 0 below.
390+
microtaskQueue_.clear();
391+
381392
// On shutting down and cleaning up: when JSC is actually torn down,
382393
// it calls JSC::Heap::lastChanceToFinalize internally which
383394
// finalizes anything left over. But at this point,
@@ -434,7 +445,24 @@ jsi::Value JSCRuntime::evaluateJavaScript(
434445
return createValue(res);
435446
}
436447

437-
bool JSCRuntime::drainMicrotasks(int maxMicrotasksHint) {
448+
void JSCRuntime::queueMicrotask(const jsi::Function& callback) {
449+
microtaskQueue_.emplace_back(
450+
jsi::Value(*this, callback).asObject(*this).asFunction(*this));
451+
}
452+
453+
bool JSCRuntime::drainMicrotasks(int /*maxMicrotasksHint*/) {
454+
// Note that new jobs can be enqueued during the draining.
455+
while (!microtaskQueue_.empty()) {
456+
jsi::Function callback = std::move(microtaskQueue_.front());
457+
458+
// We need to pop before calling the callback because that might throw.
459+
// When that happens, the host will call `drainMicrotasks` again to execute
460+
// the remaining microtasks, and this one shouldn't run again.
461+
microtaskQueue_.pop_front();
462+
463+
callback.call(*this);
464+
}
465+
438466
return true;
439467
}
440468

0 commit comments

Comments
 (0)