mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
e612d3a116
commit
27304fcd0b
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
inline static void handleFatalError(
|
||||
jsi::Runtime &runtime,
|
||||
const jsi::JSError &error) {
|
||||
auto reportFatalError = "reportFatalError";
|
||||
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
|
||||
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
|
||||
!errorUtils.getObject(runtime).hasProperty(runtime, reportFatalError)) {
|
||||
// ErrorUtils was not set up. This probably means the bundle didn't
|
||||
// load properly.
|
||||
throw jsi::JSError(
|
||||
runtime,
|
||||
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
|
||||
error.getMessage(),
|
||||
error.getStack());
|
||||
}
|
||||
|
||||
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(
|
||||
runtime, reportFatalError);
|
||||
|
||||
func.call(runtime, error.value());
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "RuntimeScheduler.h"
|
||||
#include "ErrorUtils.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
@@ -89,27 +90,32 @@ void RuntimeScheduler::executeNowOnTheSameThread(
|
||||
void RuntimeScheduler::startWorkLoop(jsi::Runtime &runtime) const {
|
||||
auto previousPriority = currentPriority_;
|
||||
isPerformingWork_ = true;
|
||||
while (!taskQueue_.empty()) {
|
||||
auto topPriorityTask = taskQueue_.top();
|
||||
auto now = now_();
|
||||
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
|
||||
try {
|
||||
while (!taskQueue_.empty()) {
|
||||
auto topPriorityTask = taskQueue_.top();
|
||||
auto now = now_();
|
||||
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
|
||||
|
||||
if (!didUserCallbackTimeout && shouldYield_) {
|
||||
// This task hasn't expired and we need to yield.
|
||||
break;
|
||||
}
|
||||
currentPriority_ = topPriorityTask->priority;
|
||||
auto result = topPriorityTask->execute(runtime);
|
||||
if (!didUserCallbackTimeout && shouldYield_) {
|
||||
// This task hasn't expired and we need to yield.
|
||||
break;
|
||||
}
|
||||
currentPriority_ = topPriorityTask->priority;
|
||||
auto result = topPriorityTask->execute(runtime);
|
||||
|
||||
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
|
||||
topPriorityTask->callback =
|
||||
result.getObject(runtime).getFunction(runtime);
|
||||
} else {
|
||||
if (taskQueue_.top() == topPriorityTask) {
|
||||
taskQueue_.pop();
|
||||
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
|
||||
topPriorityTask->callback =
|
||||
result.getObject(runtime).getFunction(runtime);
|
||||
} else {
|
||||
if (taskQueue_.top() == topPriorityTask) {
|
||||
taskQueue_.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (jsi::JSError &error) {
|
||||
handleFatalError(runtime, error);
|
||||
}
|
||||
|
||||
currentPriority_ = previousPriority;
|
||||
isPerformingWork_ = false;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||||
#include <memory>
|
||||
|
||||
#include "StubClock.h"
|
||||
#include "StubErrorUtils.h"
|
||||
#include "StubQueue.h"
|
||||
|
||||
namespace facebook::react {
|
||||
@@ -22,6 +24,7 @@ class RuntimeSchedulerTest : public testing::Test {
|
||||
void SetUp() override {
|
||||
hostFunctionCallCount_ = 0;
|
||||
runtime_ = facebook::hermes::makeHermesRuntime();
|
||||
stubErrorUtils_ = StubErrorUtils::createAndInstallIfNeeded(*runtime_);
|
||||
stubQueue_ = std::make_unique<StubQueue>();
|
||||
|
||||
RuntimeExecutor runtimeExecutor =
|
||||
@@ -52,7 +55,7 @@ class RuntimeSchedulerTest : public testing::Test {
|
||||
jsi::Runtime &,
|
||||
jsi::Value const &,
|
||||
jsi::Value const *arguments,
|
||||
size_t) noexcept -> jsi::Value {
|
||||
size_t) -> jsi::Value {
|
||||
++hostFunctionCallCount_;
|
||||
auto didUserCallbackTimeout = arguments[0].getBool();
|
||||
return callback(didUserCallbackTimeout);
|
||||
@@ -65,6 +68,7 @@ class RuntimeSchedulerTest : public testing::Test {
|
||||
std::unique_ptr<StubClock> stubClock_;
|
||||
std::unique_ptr<StubQueue> stubQueue_;
|
||||
std::unique_ptr<RuntimeScheduler> runtimeScheduler_;
|
||||
std::shared_ptr<StubErrorUtils> stubErrorUtils_;
|
||||
};
|
||||
|
||||
TEST_F(RuntimeSchedulerTest, now) {
|
||||
@@ -469,4 +473,25 @@ TEST_F(RuntimeSchedulerTest, scheduleTaskFromTask) {
|
||||
EXPECT_EQ(stubQueue_->size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(RuntimeSchedulerTest, handlingError) {
|
||||
bool didRunTask = false;
|
||||
auto firstCallback = createHostFunctionFromLambda([this, &didRunTask](bool) {
|
||||
didRunTask = true;
|
||||
jsi::detail::throwJSError(*runtime_, "Test error");
|
||||
return jsi::Value::undefined();
|
||||
});
|
||||
|
||||
runtimeScheduler_->scheduleTask(
|
||||
SchedulerPriority::NormalPriority, std::move(firstCallback));
|
||||
|
||||
EXPECT_FALSE(didRunTask);
|
||||
EXPECT_EQ(stubQueue_->size(), 1);
|
||||
|
||||
stubQueue_->tick();
|
||||
|
||||
EXPECT_TRUE(didRunTask);
|
||||
EXPECT_EQ(stubQueue_->size(), 0);
|
||||
EXPECT_EQ(stubErrorUtils_->getReportFatalCallCount(), 1);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* Exposes StubErrorUtils to JavaScript realm.
|
||||
*/
|
||||
class StubErrorUtils : public jsi::HostObject {
|
||||
public:
|
||||
static std::shared_ptr<StubErrorUtils> createAndInstallIfNeeded(
|
||||
jsi::Runtime &runtime) {
|
||||
auto errorUtilsModuleName = "ErrorUtils";
|
||||
auto errorUtilsValue =
|
||||
runtime.global().getProperty(runtime, errorUtilsModuleName);
|
||||
|
||||
if (errorUtilsValue.isUndefined()) {
|
||||
auto stubErrorUtils = std::make_shared<StubErrorUtils>();
|
||||
auto object = jsi::Object::createFromHostObject(runtime, stubErrorUtils);
|
||||
runtime.global().setProperty(
|
||||
runtime, errorUtilsModuleName, std::move(object));
|
||||
return stubErrorUtils;
|
||||
}
|
||||
|
||||
auto stubErrorUtilsObject = errorUtilsValue.asObject(runtime);
|
||||
return stubErrorUtilsObject.getHostObject<StubErrorUtils>(runtime);
|
||||
}
|
||||
|
||||
/*
|
||||
* `jsi::HostObject` specific overloads.
|
||||
*/
|
||||
jsi::Value get(jsi::Runtime &runtime, jsi::PropNameID const &name) override {
|
||||
auto propertyName = name.utf8(runtime);
|
||||
|
||||
if (propertyName == "reportFatalError") {
|
||||
return jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
name,
|
||||
1,
|
||||
[this](
|
||||
jsi::Runtime &runtime,
|
||||
jsi::Value const &,
|
||||
jsi::Value const *arguments,
|
||||
size_t) noexcept -> jsi::Value {
|
||||
reportFatalCallCount_++;
|
||||
return jsi::Value::undefined();
|
||||
});
|
||||
}
|
||||
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
|
||||
int getReportFatalCallCount() const {
|
||||
return reportFatalCallCount_;
|
||||
}
|
||||
|
||||
private:
|
||||
int reportFatalCallCount_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
Reference in New Issue
Block a user