Skip to content

Commit 27304fc

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
Add error handling to RuntimeScheduler
Summary: changelog: [internal] Catch JavaScript errors and forward them to `ErrorUtils` in *RuntimeScheduler*. This makes sure that JS errors are handled by ErrorUtils and do not bubble up to bridge. Reviewed By: philIip Differential Revision: D31429001 fbshipit-source-id: 50f865872e4cd3ba180056099ff40f5962ee7a77
1 parent e612d3a commit 27304fc

File tree

4 files changed

+158
-19
lines changed

4 files changed

+158
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <jsi/jsi.h>
9+
10+
namespace facebook {
11+
namespace react {
12+
13+
inline static void handleFatalError(
14+
jsi::Runtime &runtime,
15+
const jsi::JSError &error) {
16+
auto reportFatalError = "reportFatalError";
17+
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
18+
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
19+
!errorUtils.getObject(runtime).hasProperty(runtime, reportFatalError)) {
20+
// ErrorUtils was not set up. This probably means the bundle didn't
21+
// load properly.
22+
throw jsi::JSError(
23+
runtime,
24+
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
25+
error.getMessage(),
26+
error.getStack());
27+
}
28+
29+
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(
30+
runtime, reportFatalError);
31+
32+
func.call(runtime, error.value());
33+
}
34+
35+
} // namespace react
36+
} // namespace facebook

ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp

+24-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#include "RuntimeScheduler.h"
9+
#include "ErrorUtils.h"
910

1011
namespace facebook {
1112
namespace react {
@@ -89,27 +90,32 @@ void RuntimeScheduler::executeNowOnTheSameThread(
8990
void RuntimeScheduler::startWorkLoop(jsi::Runtime &runtime) const {
9091
auto previousPriority = currentPriority_;
9192
isPerformingWork_ = true;
92-
while (!taskQueue_.empty()) {
93-
auto topPriorityTask = taskQueue_.top();
94-
auto now = now_();
95-
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
96-
97-
if (!didUserCallbackTimeout && shouldYield_) {
98-
// This task hasn't expired and we need to yield.
99-
break;
100-
}
101-
currentPriority_ = topPriorityTask->priority;
102-
auto result = topPriorityTask->execute(runtime);
103-
104-
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
105-
topPriorityTask->callback =
106-
result.getObject(runtime).getFunction(runtime);
107-
} else {
108-
if (taskQueue_.top() == topPriorityTask) {
109-
taskQueue_.pop();
93+
try {
94+
while (!taskQueue_.empty()) {
95+
auto topPriorityTask = taskQueue_.top();
96+
auto now = now_();
97+
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
98+
99+
if (!didUserCallbackTimeout && shouldYield_) {
100+
// This task hasn't expired and we need to yield.
101+
break;
102+
}
103+
currentPriority_ = topPriorityTask->priority;
104+
auto result = topPriorityTask->execute(runtime);
105+
106+
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
107+
topPriorityTask->callback =
108+
result.getObject(runtime).getFunction(runtime);
109+
} else {
110+
if (taskQueue_.top() == topPriorityTask) {
111+
taskQueue_.pop();
112+
}
110113
}
111114
}
115+
} catch (jsi::JSError &error) {
116+
handleFatalError(runtime, error);
112117
}
118+
113119
currentPriority_ = previousPriority;
114120
isPerformingWork_ = false;
115121
}

ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include <jsi/jsi.h>
1111
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
1212
#include <memory>
13+
1314
#include "StubClock.h"
15+
#include "StubErrorUtils.h"
1416
#include "StubQueue.h"
1517

1618
namespace facebook::react {
@@ -22,6 +24,7 @@ class RuntimeSchedulerTest : public testing::Test {
2224
void SetUp() override {
2325
hostFunctionCallCount_ = 0;
2426
runtime_ = facebook::hermes::makeHermesRuntime();
27+
stubErrorUtils_ = StubErrorUtils::createAndInstallIfNeeded(*runtime_);
2528
stubQueue_ = std::make_unique<StubQueue>();
2629

2730
RuntimeExecutor runtimeExecutor =
@@ -52,7 +55,7 @@ class RuntimeSchedulerTest : public testing::Test {
5255
jsi::Runtime &,
5356
jsi::Value const &,
5457
jsi::Value const *arguments,
55-
size_t) noexcept -> jsi::Value {
58+
size_t) -> jsi::Value {
5659
++hostFunctionCallCount_;
5760
auto didUserCallbackTimeout = arguments[0].getBool();
5861
return callback(didUserCallbackTimeout);
@@ -65,6 +68,7 @@ class RuntimeSchedulerTest : public testing::Test {
6568
std::unique_ptr<StubClock> stubClock_;
6669
std::unique_ptr<StubQueue> stubQueue_;
6770
std::unique_ptr<RuntimeScheduler> runtimeScheduler_;
71+
std::shared_ptr<StubErrorUtils> stubErrorUtils_;
6872
};
6973

7074
TEST_F(RuntimeSchedulerTest, now) {
@@ -469,4 +473,25 @@ TEST_F(RuntimeSchedulerTest, scheduleTaskFromTask) {
469473
EXPECT_EQ(stubQueue_->size(), 0);
470474
}
471475

476+
TEST_F(RuntimeSchedulerTest, handlingError) {
477+
bool didRunTask = false;
478+
auto firstCallback = createHostFunctionFromLambda([this, &didRunTask](bool) {
479+
didRunTask = true;
480+
jsi::detail::throwJSError(*runtime_, "Test error");
481+
return jsi::Value::undefined();
482+
});
483+
484+
runtimeScheduler_->scheduleTask(
485+
SchedulerPriority::NormalPriority, std::move(firstCallback));
486+
487+
EXPECT_FALSE(didRunTask);
488+
EXPECT_EQ(stubQueue_->size(), 1);
489+
490+
stubQueue_->tick();
491+
492+
EXPECT_TRUE(didRunTask);
493+
EXPECT_EQ(stubQueue_->size(), 0);
494+
EXPECT_EQ(stubErrorUtils_->getReportFatalCallCount(), 1);
495+
}
496+
472497
} // namespace facebook::react
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <glog/logging.h>
11+
#include <jsi/jsi.h>
12+
13+
namespace facebook {
14+
namespace react {
15+
16+
/*
17+
* Exposes StubErrorUtils to JavaScript realm.
18+
*/
19+
class StubErrorUtils : public jsi::HostObject {
20+
public:
21+
static std::shared_ptr<StubErrorUtils> createAndInstallIfNeeded(
22+
jsi::Runtime &runtime) {
23+
auto errorUtilsModuleName = "ErrorUtils";
24+
auto errorUtilsValue =
25+
runtime.global().getProperty(runtime, errorUtilsModuleName);
26+
27+
if (errorUtilsValue.isUndefined()) {
28+
auto stubErrorUtils = std::make_shared<StubErrorUtils>();
29+
auto object = jsi::Object::createFromHostObject(runtime, stubErrorUtils);
30+
runtime.global().setProperty(
31+
runtime, errorUtilsModuleName, std::move(object));
32+
return stubErrorUtils;
33+
}
34+
35+
auto stubErrorUtilsObject = errorUtilsValue.asObject(runtime);
36+
return stubErrorUtilsObject.getHostObject<StubErrorUtils>(runtime);
37+
}
38+
39+
/*
40+
* `jsi::HostObject` specific overloads.
41+
*/
42+
jsi::Value get(jsi::Runtime &runtime, jsi::PropNameID const &name) override {
43+
auto propertyName = name.utf8(runtime);
44+
45+
if (propertyName == "reportFatalError") {
46+
return jsi::Function::createFromHostFunction(
47+
runtime,
48+
name,
49+
1,
50+
[this](
51+
jsi::Runtime &runtime,
52+
jsi::Value const &,
53+
jsi::Value const *arguments,
54+
size_t) noexcept -> jsi::Value {
55+
reportFatalCallCount_++;
56+
return jsi::Value::undefined();
57+
});
58+
}
59+
60+
return jsi::Value::undefined();
61+
}
62+
63+
int getReportFatalCallCount() const {
64+
return reportFatalCallCount_;
65+
}
66+
67+
private:
68+
int reportFatalCallCount_;
69+
};
70+
71+
} // namespace react
72+
} // namespace facebook

0 commit comments

Comments
 (0)