Skip to content

Commit 7dd6549

Browse files
rubennorteTitozzz
authored andcommitted
Continue running microtasks when parent task throws
Summary: Changelog: [General][Fixed] Fixed LogBox not showing correctly on the New Architecture We found an incorrect behavior in the event loop, where an error in a task would prevent its microtasks from running. This isn't spec compliant and should be fixed. This caused LogBox to not work correctly, as error reporting is implemented via microtasks that would never execute. Reviewed By: sammy-SC Differential Revision: D58010521 fbshipit-source-id: 7901c5d6e83fb63af148e12ad6c32be490a3999d
1 parent 487c848 commit 7dd6549

File tree

3 files changed

+74
-26
lines changed

3 files changed

+74
-26
lines changed

packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -229,21 +229,17 @@ void RuntimeScheduler_Modern::startWorkLoop(
229229

230230
auto previousPriority = currentPriority_;
231231

232-
try {
233-
while (syncTaskRequests_ == 0) {
234-
auto currentTime = now_();
235-
auto topPriorityTask = selectTask(currentTime, onlyExpired);
236-
237-
if (!topPriorityTask) {
238-
// No pending work to do.
239-
// Events will restart the loop when necessary.
240-
break;
241-
}
242-
243-
executeTask(runtime, *topPriorityTask, currentTime);
232+
while (syncTaskRequests_ == 0) {
233+
auto currentTime = now_();
234+
auto topPriorityTask = selectTask(currentTime, onlyExpired);
235+
236+
if (!topPriorityTask) {
237+
// No pending work to do.
238+
// Events will restart the loop when necessary.
239+
break;
244240
}
245-
} catch (jsi::JSError& error) {
246-
handleJSError(runtime, error, true);
241+
242+
executeTask(runtime, *topPriorityTask, currentTime);
247243
}
248244

249245
currentPriority_ = previousPriority;
@@ -330,12 +326,16 @@ void RuntimeScheduler_Modern::executeMacrotask(
330326
bool didUserCallbackTimeout) const {
331327
SystraceSection s("RuntimeScheduler::executeMacrotask");
332328

333-
auto result = task.execute(runtime, didUserCallbackTimeout);
329+
try {
330+
auto result = task.execute(runtime, didUserCallbackTimeout);
334331

335-
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
336-
// If the task returned a continuation callback, we re-assign it to the task
337-
// and keep the task in the queue.
338-
task.callback = result.getObject(runtime).getFunction(runtime);
332+
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
333+
// If the task returned a continuation callback, we re-assign it to the
334+
// task and keep the task in the queue.
335+
task.callback = result.getObject(runtime).getFunction(runtime);
336+
}
337+
} catch (jsi::JSError& error) {
338+
handleJSError(runtime, error, true);
339339
}
340340
}
341341

packages/react-native/ReactCommon/react/renderer/runtimescheduler/Task.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,22 @@ jsi::Value Task::execute(jsi::Runtime& runtime, bool didUserCallbackTimeout) {
3232
return result;
3333
}
3434

35-
auto& cbVal = callback.value();
35+
// We get the value of the callback and reset it immediately to avoid it being
36+
// called more than once (including when the callback throws).
37+
auto originalCallback = std::move(*callback);
38+
callback.reset();
3639

37-
if (cbVal.index() == 0) {
40+
if (originalCallback.index() == 0) {
3841
// Callback in JavaScript is expecting a single bool parameter.
3942
// React team plans to remove it in the future when a scheduler bug on web
4043
// is resolved.
41-
result =
42-
std::get<jsi::Function>(cbVal).call(runtime, {didUserCallbackTimeout});
44+
result = std::get<jsi::Function>(originalCallback)
45+
.call(runtime, {didUserCallbackTimeout});
4346
} else {
4447
// Calling a raw callback
45-
std::get<RawCallback>(cbVal)(runtime);
48+
std::get<RawCallback>(originalCallback)(runtime);
4649
}
47-
// Destroying callback to prevent calling it twice.
48-
callback.reset();
50+
4951
return result;
5052
}
5153

packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,52 @@ TEST_P(RuntimeSchedulerTest, modernTwoThreadsRequestAccessToTheRuntime) {
10211021
EXPECT_EQ(stubQueue_->size(), 0);
10221022
}
10231023

1024+
TEST_P(RuntimeSchedulerTest, errorInTaskShouldNotStopMicrotasks) {
1025+
// Only for modern runtime scheduler
1026+
if (!GetParam()) {
1027+
return;
1028+
}
1029+
1030+
auto microtaskRan = false;
1031+
auto taskRan = false;
1032+
1033+
auto callback = createHostFunctionFromLambda([&](bool /* unused */) {
1034+
taskRan = true;
1035+
1036+
auto microtaskCallback = jsi::Function::createFromHostFunction(
1037+
*runtime_,
1038+
jsi::PropNameID::forUtf8(*runtime_, "microtask1"),
1039+
3,
1040+
[&](jsi::Runtime& /*unused*/,
1041+
const jsi::Value& /*unused*/,
1042+
const jsi::Value* /*arguments*/,
1043+
size_t /*unused*/) -> jsi::Value {
1044+
microtaskRan = true;
1045+
return jsi::Value::undefined();
1046+
});
1047+
1048+
runtime_->queueMicrotask(microtaskCallback);
1049+
1050+
throw jsi::JSError(*runtime_, "Test error");
1051+
1052+
return jsi::Value::undefined();
1053+
});
1054+
1055+
runtimeScheduler_->scheduleTask(
1056+
SchedulerPriority::NormalPriority, std::move(callback));
1057+
1058+
EXPECT_EQ(taskRan, false);
1059+
EXPECT_EQ(microtaskRan, false);
1060+
EXPECT_EQ(stubQueue_->size(), 1);
1061+
1062+
stubQueue_->tick();
1063+
1064+
EXPECT_EQ(taskRan, 1);
1065+
EXPECT_EQ(microtaskRan, 1);
1066+
EXPECT_EQ(stubQueue_->size(), 0);
1067+
EXPECT_EQ(stubErrorUtils_->getReportFatalCallCount(), 1);
1068+
}
1069+
10241070
INSTANTIATE_TEST_SUITE_P(
10251071
UseModernRuntimeScheduler,
10261072
RuntimeSchedulerTest,

0 commit comments

Comments
 (0)