Skip to content

Commit

Permalink
Route all js errors caught in C++ through JsErrorHandler
Browse files Browse the repository at this point in the history
Summary:
If any fatal js error is caught in c++, just route it through js error handler.

Then, make js error handler call into the right pipeline:
1. After the js pipeline is ready: Route the errors through the js pipeline
2. Otherwise: Route errors through the c++ pipeline.

Changelog: [Internal]

Differential Revision: D60138417
  • Loading branch information
RSNara authored and facebook-github-bot committed Jul 23, 2024
1 parent 924c4c1 commit bc5c487
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "JsErrorHandler.h"
#include <cxxreact/ErrorUtils.h>
#include <regex>
#include <sstream>
#include <string>
Expand Down Expand Up @@ -96,10 +97,18 @@ JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)

JsErrorHandler::~JsErrorHandler() {}

void JsErrorHandler::handleFatalError(const jsi::JSError& error) {
void JsErrorHandler::handleFatalError(
jsi::Runtime& runtime,
jsi::JSError& error) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup.
_hasHandledFatalError = true;

if (_useJSPipeline) {
handleJSError(runtime, error, true);
return;
}
// This is a hacky way to get Hermes stack trace.
ParsedError parsedError = parseErrorStack(error, true, false);
_onJsError(parsedError);
}
Expand All @@ -108,4 +117,8 @@ bool JsErrorHandler::hasHandledFatalError() {
return _hasHandledFatalError;
}

void JsErrorHandler::useJSPipeline() {
_useJSPipeline = true;
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ class JsErrorHandler {
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();

void handleFatalError(const jsi::JSError& error);
void handleFatalError(jsi::Runtime& runtime, jsi::JSError& error);
bool hasHandledFatalError();
void useJSPipeline();

private:
OnJsError _onJsError;
bool _hasHandledFatalError;
bool _useJSPipeline{};
};

} // namespace facebook::react
68 changes: 46 additions & 22 deletions packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,32 @@ ReactInstance::ReactInstance(
}

if (auto jsThread = weakJsThread.lock()) {
jsThread->runOnQueue(
[weakRuntime, weakTimerManager, callback = std::move(callback)]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsThread->runOnQueue([weakJsErrorHander,
weakRuntime,
weakTimerManager,
callback = std::move(callback)]() {
auto jsErrorHandler = weakJsErrorHander.lock();
auto runtime = weakRuntime.lock();
if (!runtime || !jsErrorHandler) {
return;
}

jsi::Runtime& jsiRuntime = runtime->getRuntime();
SystraceSection s("ReactInstance::_runtimeExecutor[Callback]");
try {
callback(jsiRuntime);

// If we have first-class support for microtasks,
// they would've been called as part of the previous callback.
if (!ReactNativeFeatureFlags::enableMicrotasks()) {
if (auto timerManager = weakTimerManager.lock()) {
timerManager->callReactNativeMicrotasks(jsiRuntime);
}
}
} catch (jsi::JSError& originalError) {
handleJSError(jsiRuntime, originalError, true);
jsi::Runtime& jsiRuntime = runtime->getRuntime();
SystraceSection s("ReactInstance::_runtimeExecutor[Callback]");
try {
callback(jsiRuntime);

// If we have first-class support for microtasks,
// they would've been called as part of the previous callback.
if (!ReactNativeFeatureFlags::enableMicrotasks()) {
if (auto timerManager = weakTimerManager.lock()) {
timerManager->callReactNativeMicrotasks(jsiRuntime);
}
});
}
} catch (jsi::JSError& originalError) {
jsErrorHandler->handleFatalError(jsiRuntime, originalError);
}
});
}
};

Expand Down Expand Up @@ -118,7 +121,25 @@ ReactInstance::ReactInstance(
};
}

runtimeScheduler_ = std::make_shared<RuntimeScheduler>(runtimeExecutor);
runtimeScheduler_ = std::make_shared<RuntimeScheduler>(
runtimeExecutor,
RuntimeSchedulerClock::now,
[weakJsErrorHandler = std::weak_ptr(jsErrorHandler_)](
jsi::Runtime& runtime, jsi::JSError& error) {
auto jsErrorHandler = weakJsErrorHandler.lock();
if (!jsErrorHandler) {
return;
}
jsErrorHandler->handleFatalError(runtime, error);
},
[weakJsErrorHandler = std::weak_ptr(jsErrorHandler_)](
jsi::Runtime& runtime, jsi::JSError& error) {
auto jsErrorHandler = weakJsErrorHandler.lock();
if (!jsErrorHandler) {
return;
}
jsErrorHandler->handleFatalError(runtime, error);
});
runtimeScheduler_->setPerformanceEntryReporter(
// FIXME: Move creation of PerformanceEntryReporter to here and guarantee
// that its lifetime is the same as the runtime.
Expand Down Expand Up @@ -212,6 +233,9 @@ void ReactInstance::loadScript(
}

runtime.evaluateJavaScript(buffer, sourceURL);
if (!jsErrorHandler_->hasHandledFatalError()) {
jsErrorHandler_->useJSPipeline();
}
if (hasLogger) {
ReactMarker::logTaggedMarkerBridgeless(
ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
Expand Down

0 comments on commit bc5c487

Please sign in to comment.