Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hermes reference tracking #563

Merged
merged 22 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions jvm/hermes/src/main/jni/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ cc_library(
"JHermesRuntime.cpp",
"JJSIValue.cpp",
"OnLoad.cpp",
"RuntimeScope.cpp",
],
hdrs = [
"JHermesRuntime.h",
"JJSIValue.h",
"RuntimeScope.h",
],
copts = CMAKE_BUILD_TYPE_COPTS + select({
# TODO: Using React Natives JSI doesn't contain microtask - come back to this if we build it ourselves
Expand Down
52 changes: 26 additions & 26 deletions jvm/hermes/src/main/jni/JHermesRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,28 +49,7 @@ class JHermesRuntime : public HybridClass<JHermesRuntime, JJSIRuntime> {

// TODO: Add the rest of the HermesRuntime API (like loading bytecode)
local_ref<JJSIValue::jhybridobject> evaluateJavaScriptWithSourceMap(std::string script, std::string sourceMap, std::string sourceURL) {
auto result = JJSIValue::newObjectCxxArgs(get_runtime().evaluateJavaScriptWithSourceMap(std::make_shared<StringBuffer>(script), std::make_shared<StringBuffer>(sourceMap), sourceURL));
trackRef(result);
return result;
}

void trackRef(alias_ref<JHybridClass::jhybridobject> ref) override {
// TODO: From my current understanding, holding a global prevents the class from being
// GC'd when it goes out of scope in Java land, which would prevent the JSI Value reference
// from being reset. I originally wanted to keep a weak_ref, but was getting intermittent
// segfaults from untracked (or incorrectly tracked) references that were then not being
// released because the weak_ref was out of scope. The assumption there was that if the
// weak_ref was out of scope, it was already released. But this doesn't seem to be true,
// otherwise we wouldn't be getting segfaults. Making them global does prevent the segfaults,
// reaffirming the above. Since all the wrapper classes are holding are pointers
// this probably isn't a huge deal, but is certainly an inefficiency we should look
// to remove. I'd like to just create weak_ptrs on the JSI values themselves,
// which requires the holders to use shared_ptrs and expose a getter for accessing.
// Then we'd also need a way to hold arbitrary weak_ptrs. We could also maybe try
// to get weak refs to _just_ the CXX parts of the hybrid class to avoid leaking things
// on the JVM side?
// TODO: Maybe we could do a periodic pruning of the vector to remove obsolete refs?
Comment on lines -58 to -72
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful - removing longstanding paragraphs of TODOs are always great to see.

scope_.push_back(make_global(ref));
return JJSIValue::newObjectCxxArgs(this->runtimeScope_, get_runtime().evaluateJavaScriptWithSourceMap(std::make_shared<StringBuffer>(script), std::make_shared<StringBuffer>(sourceMap), sourceURL));;
}

~JHermesRuntime() override {
Expand All @@ -79,9 +58,26 @@ class JHermesRuntime : public HybridClass<JHermesRuntime, JJSIRuntime> {
}

void release() {
for (auto& ref : scope_) {
ref->cthis()->release();
for (auto& ptr_pair : *runtimeScope_->valueScope) {
ptr_pair.second.reset();
}
for (auto& ptr_pair : *runtimeScope_->objectScope) {
ptr_pair.second.reset();
}
for (auto& ptr_pair : *runtimeScope_->functionScope) {
ptr_pair.second.reset();
}
for (auto& ptr_pair : *runtimeScope_->arrayScope) {
ptr_pair.second.reset();
}
for (auto& ptr_pair : *runtimeScope_->symbolScope) {
ptr_pair.second.reset();
}
runtimeScope_->valueScope->clear();
runtimeScope_->objectScope->clear();
runtimeScope_->functionScope->clear();
runtimeScope_->arrayScope->clear();
runtimeScope_->symbolScope->clear();
if (jConfig_) jConfig_.reset();
if (runtime_) runtime_.reset();
}
Expand All @@ -96,6 +92,10 @@ class JHermesRuntime : public HybridClass<JHermesRuntime, JJSIRuntime> {
throwNativeHandleReleasedException("HermesRuntime");
}

shared_ptr<RuntimeScope> get_scope() override {
return runtimeScope_;
}

operator facebook::jsi::Runtime&() { return get_runtime(); }

local_ref<JHermesConfig::jhybridobject> get_config() {
Expand All @@ -108,11 +108,11 @@ class JHermesRuntime : public HybridClass<JHermesRuntime, JJSIRuntime> {
friend HybridBase;
std::unique_ptr<HermesRuntime> runtime_;
global_ref<JHermesConfig::jhybridobject> jConfig_;
std::vector<global_ref<JHybridClass::jhybridobject>> scope_;
shared_ptr<RuntimeScope> runtimeScope_;
explicit JHermesRuntime(
std::unique_ptr<HermesRuntime> runtime,
alias_ref<JHermesConfig::jhybridobject> jConfig
) : HybridClass(), runtime_(std::move(runtime)), jConfig_(make_global(jConfig)), scope_() {}
) : HybridClass(), runtime_(std::move(runtime)), jConfig_(make_global(jConfig)), runtimeScope_(make_shared<RuntimeScope>()) {}

explicit JHermesRuntime(alias_ref<JHermesConfig::jhybridobject> jConfig) : JHermesRuntime(makeHermesRuntime(jConfig->cthis()->get_config()), jConfig) {}
explicit JHermesRuntime() : JHermesRuntime(JHermesConfig::create(JHermesConfig::javaClassStatic())) {}
Expand Down
Loading