mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: ## The Problem 1. `CatalystInstanceImpl` indirectly holds on to the `jsi::Runtime`. When you destroy `CatalystInstanceImpl`, you destroy the `jsi::Runtime`. As a part of reloading React Native, we destroy and re-create `CatalystInstanceImpl`, which destroys and re-creates the `jsi::Runtime`. 2. When JS passes in a callback to a TurboModule method, we take that callback (a `jsi::Function`) and wrap it in a Java `Callback` (implemented by `JCxxCallbackImpl`). This Java `Callback`, when executed, schedules the `jsi::Function` to be invoked on a Java thread at a later point in time. **Note:** The Java NativeModule can hold on to the Java `Callback` (and, by transitivity, the `jsi::Function`) for potentially forever. 3. It is a requirement of `jsi::Runtime` that all objects associated with the Runtime (ex: `jsi::Function`) must be destroyed before the Runtime itself is destroyed. See: https://fburl.com/m3mqk6wt ### jsi.h ``` /// .................................................... In addition, to /// make shutdown safe, destruction of objects associated with the Runtime /// must be destroyed before the Runtime is destroyed, or from the /// destructor of a managed HostObject or HostFunction. Informally, this /// means that the main source of unsafe behavior is to hold a jsi object /// in a non-Runtime-managed object, and not clean it up before the Runtime /// is shut down. If your lifecycle is such that avoiding this is hard, /// you will probably need to do use your own locks. class Runtime { public: virtual ~Runtime(); ``` Therefore, when you delete `CatalystInstanceImpl`, you could end up with a situation where the `jsi::Runtime` is destroyed before all `jsi::Function`s are destroyed. In dev, this leads the program to crash when you reload the app after having used a TurboModule method that uses callbacks. ## The Solution If the only reference to a `HostObject` or a `HostFunction` is in the JS Heap, then the `HostObject` and `HostFunction` destructors can destroy JSI objects. The TurboModule cache is the only thing, aside from the JS Heap, that holds a reference to all C++ TurboModules. But that cache (and the entire native side of `TurboModuleManager`) is destroyed when we call `mHybridData.resetNative()` in `TurboModuleManager.onCatalystInstanceDestroy()` in D16552730. (I verified this by commenting out `mHybridData.resetNative()` and placing a breakpoint in the destructor of `JavaTurboModule`). So, when we're cleaning up `TurboModuleManager`, the only reference to a Java TurboModule is the JS Heap. Therefore, it's safe and correct for us to destroy all `jsi::Function`s created by the Java TurboModule in `~JavaTurboModule`. So, in this diff, I keep a set of all `CallbackWrappers`, and explicitly call `destroy()` on them in the `JavaTurboModule` destructor. Note that since `~JavaTurboModule` accesses `callbackWrappers_`, it must be executed on the JS Thread, since `createJavaCallbackFromJSIFunction` also accesses `callbackWrappers_` on the JS Thread. For additional safety, I also eagerly destroyed the `jsi::Function` after it's been invoked once. I'm not yet sure if we only want JS callbacks to only ever be invoked once. So, I've created a Task to document this work: T48128233. Reviewed By: mdvacca Differential Revision: D16623340 fbshipit-source-id: 3a4c3efc70b9b3c8d329f19fdf4b4423c489695b
96 lines
2.1 KiB
C++
96 lines
2.1 KiB
C++
/**
|
|
* 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 <cassert>
|
|
#include <string>
|
|
|
|
#include <folly/Optional.h>
|
|
#include <jsi/jsi.h>
|
|
|
|
#include <ReactCommon/JSCallInvoker.h>
|
|
|
|
using namespace facebook;
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
jsi::Object deepCopyJSIObject(jsi::Runtime &rt, const jsi::Object &obj);
|
|
jsi::Array deepCopyJSIArray(jsi::Runtime &rt, const jsi::Array &arr);
|
|
|
|
struct Promise {
|
|
Promise(jsi::Runtime &rt, jsi::Function resolve, jsi::Function reject);
|
|
|
|
void resolve(const jsi::Value &result);
|
|
void reject(const std::string &error);
|
|
|
|
jsi::Runtime &runtime_;
|
|
jsi::Function resolve_;
|
|
jsi::Function reject_;
|
|
};
|
|
|
|
using PromiseSetupFunctionType =
|
|
std::function<void(jsi::Runtime &rt, std::shared_ptr<Promise>)>;
|
|
jsi::Value createPromiseAsJSIValue(
|
|
jsi::Runtime &rt,
|
|
const PromiseSetupFunctionType func);
|
|
|
|
// Helper for passing jsi::Function arg to other methods.
|
|
class CallbackWrapper {
|
|
private:
|
|
struct Data {
|
|
Data(
|
|
jsi::Function callback,
|
|
jsi::Runtime &runtime,
|
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
|
: callback(std::move(callback)),
|
|
runtime(runtime),
|
|
jsInvoker(std::move(jsInvoker)) {}
|
|
|
|
jsi::Function callback;
|
|
jsi::Runtime &runtime;
|
|
std::shared_ptr<react::JSCallInvoker> jsInvoker;
|
|
};
|
|
|
|
folly::Optional<Data> data_;
|
|
|
|
public:
|
|
CallbackWrapper(
|
|
jsi::Function callback,
|
|
jsi::Runtime &runtime,
|
|
std::shared_ptr<react::JSCallInvoker> jsInvoker)
|
|
: data_(Data{std::move(callback), runtime, jsInvoker}) {}
|
|
|
|
// Delete the enclosed jsi::Function
|
|
void destroy() {
|
|
data_ = folly::none;
|
|
}
|
|
|
|
bool isDestroyed() {
|
|
return !data_.hasValue();
|
|
}
|
|
|
|
jsi::Function &callback() {
|
|
assert(!isDestroyed());
|
|
return data_->callback;
|
|
}
|
|
|
|
jsi::Runtime &runtime() {
|
|
assert(!isDestroyed());
|
|
return data_->runtime;
|
|
}
|
|
|
|
react::JSCallInvoker &jsInvoker() {
|
|
assert(!isDestroyed());
|
|
return *(data_->jsInvoker);
|
|
}
|
|
};
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|