GeezerVison is showing github.com — fix: tear down a failed runtime bootstrap so it can be retried by adrian-niculescu · Pull Request #2047 · NativeScript/android
fix: tear down a failed runtime bootstrap so it can be retried
#2047
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
Uh oh!
There was an error while loading. Please reload this page.
Show file tree
Hide file tree
Changes from all commits
Commits
File filter
Filter by extension
.cpp
(4)
.h
(2)
.java
(1)
All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Hide whitespace
Diff view
Hide whitespace
There are no files selected for viewing
Original file line number
Diff line number
Diff line change
Expand Up
@@ -1058,7 +1058,11 @@ JsV8InspectorClient* JsV8InspectorClient::GetInstance() {
// handleMessageOnSocketThread also calls this from the socket thread, so a
// concurrent first call is possible: construct, then publish with a CAS and
// discard our copy if another thread won the race.
auto* created = new JsV8InspectorClient(Runtime::GetRuntime(0)->GetIsolate());
Runtime* mainRuntime = Runtime::GetMainRuntime();
if (mainRuntime == nullptr) {
throw NativeScriptException("Cannot create the inspector: the main runtime is not initialized");
}
auto* created = new JsV8InspectorClient(mainRuntime->GetIsolate());
JsV8InspectorClient* expected = nullptr;
if (!instance.compare_exchange_strong(expected, created, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -2247,6 +2247,8 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
throw NativeScriptException(ss.str());
}
}
// Only opened to tell a missing folder from a missing file.
closedir(dir);
string nodesFile = baseDir + "/treeNodeStream.dat";
string namesFile = baseDir + "/treeStringsStream.dat";
Expand All
@@ -2273,9 +2275,11 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
<< "-byte records. The metadata is truncated or corrupt.";
throw NativeScriptException(ss.str());
}
char* nodes = new char[lenNodes];
// Owned until the reader takes them, so a file that fails to open further
// down does not strand the buffers already read.
std::unique_ptr<char[]> nodes(new char[lenNodes]);
rewind(f);
fread(nodes, 1, lenNodes, f);
fread(nodes.get() , 1, lenNodes, f);
fclose(f);
const int _512KB = 524288;
Expand All
@@ -2290,9 +2294,9 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
}
fseek(f, 0, SEEK_END);
int lenNames = ftell(f);
char* names = new char[lenNames + _512KB];
std::unique_ptr< char[]> names( new char[lenNames + _512KB]) ;
rewind(f);
fread(names, 1, lenNames, f);
fread(names.get() , 1, lenNames, f);
fclose(f);
f = fopen(valuesFile.c_str(), "rb");
Expand Down
Expand Up
@@ -2320,11 +2324,9 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
DEBUG_WRITE("time=%ld", (millis2 - millis1));
BuildMetadata(lenNodes, reinterpret_cast<uint8_t*>(nodes), lenNames, reinterpret_cast<uint8_t*>(names), lenValues, reinterpret_cast<uint8_t*>(values));
delete[] nodes;
//delete[] names;
//delete[] values;
// The reader keeps the names and values buffers for the life of the
// process and only reads the nodes buffer while it builds the tree.
BuildMetadata(lenNodes, reinterpret_cast<uint8_t*>(nodes.get()), lenNames, reinterpret_cast<uint8_t*>(names.release()), lenValues, reinterpret_cast<uint8_t*>(values));
}
void MetadataNode::BuildMetadata(uint32_t nodesLength, uint8_t* nodeData, uint32_t nameLength, uint8_t* nameData, uint32_t valueLength, uint8_t* valueData) {
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -306,7 +306,7 @@ class ObjectManager {
static jmethodID RELEASE_NATIVE_INSTANCE_METHOD_ID;
static jmethodID RELEASE_IF_COLLECTED_METHOD_ID;
v8::Persistent<v8::Function>* m_poJsWrapperFunc;
v8::Persistent<v8::Function>* m_poJsWrapperFunc = nullptr ;
v8::Persistent<v8::ObjectTemplate> m_jsWrapperTemplate;
};
} // namespace tns
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -306,6 +306,9 @@ Runtime::~Runtime() {
s_isolate2RuntimesCache.erase(it);
}
}
// Same backstop for the breadcrumb slot Init took: the table is small and
// fixed, so slots lost to failed bootstraps would crowd out live runtimes.
CrashBreadcrumbs::UnregisterRuntime(m_id);
delete this->m_objectManager;
// idempotent backstop for the matched erase WorkerWrapper does right after
Expand Down
Expand Up
@@ -865,9 +868,11 @@ void Runtime::ElectMainRuntime() {
s_mainRuntimeElected = true;
s_mainRuntimeFailed = false;
m_isMainThread = true;
// Once per process: V8::Initialize freezes the flag list, and setting a
// flag afterwards aborts.
InitializeV8();
// Once per process, not once per election: a main runtime that failed
// hands the election back, and V8 aborts both on a second
// InitializePlatform and on a flag set after V8::Initialize froze the list.
static std::once_flag v8Initialized;
std::call_once(v8Initialized, InitializeV8);
return;
}
Expand All
@@ -888,16 +893,39 @@ void Runtime::SignalMainRuntimeReady(bool failed) {
{
std::lock_guard<std::mutex> lock(s_mainInitMutex);
if (failed) {
// Hand the election back so a later bootstrap can retry.
// Hand the election back so a later bootstrap can retry. A main runtime
// that already signalled readiness and failed afterwards withdraws it
// too, so nothing waiting on the next main runtime starts against this
// one.
s_mainRuntimeElected = false;
s_mainRuntimeFailed = true;
s_mainThreadInitialized.store(false, std::memory_order_release);
} else {
s_mainThreadInitialized.store(true, std::memory_order_release);
}
}
s_mainInitReady.notify_all();
}
void Runtime::UnwindFailedBootstrap(int runtimeId) {
Runtime* runtime = nullptr;
{
std::lock_guard<std::mutex> lock(s_runtimeCacheMutex);
auto it = s_id2RuntimeCache.find(runtimeId);
if (it != s_id2RuntimeCache.end()) {
runtime = it->second;
}
}
if (runtime == nullptr) {
return;
}
// Only the bootstrapping thread can reach this runtime: no application JS
// has run on it, so nothing has handed it to another thread or started a
// worker from it.
runtime->UnwindFailedInit();
delete runtime;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Uh oh!
There was an error while loading. Please reload this page.
}
void Runtime::UnwindFailedInit() {
/*
* Reuses the two teardown windows rather than adding a third cleanup path.
Expand All
@@ -913,6 +941,12 @@ void Runtime::UnwindFailedInit() {
DestroyRuntime();
}
m_isolate->Dispose();
// The ~Runtime backstop keys on m_isolate, which is cleared below, so the
// platform's loop entry has to go here. Left behind, it would hand the
// stopped loop to the next isolate allocated at this address.
if (m_eventLoop != nullptr) {
NativeScriptPlatform::Instance()->IsolateDisposed(m_isolate, m_eventLoop);
}
m_isolate = nullptr;
}
Expand Down
Expand Up
@@ -1229,7 +1263,15 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
// Do not build metadata (which should be static for the process) for non-main
// threads
if (m_isMainThread) {
MetadataNode::BuildMetadata(filesPath);
// Once per process, like V8 itself: the tree is process-wide state that
// outlives the runtime that built it, so a main runtime elected after an
// earlier one failed past this point reads the tree already there. Only
// the elected main runtime gets here, one at a time.
static bool metadataBuilt = false;
if (!metadataBuilt) {
MetadataNode::BuildMetadata(filesPath);
metadataBuilt = true;
}
}
auto enableProfiler = !profilerOutputDir.empty();
Expand All
@@ -1249,6 +1291,7 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
JEnv::PinThreadEnv(JEnv());
if (m_isMainThread) {
s_mainRuntime.store(this, std::memory_order_release);
// Releases any runtime waiting in ElectMainRuntime: the metadata tree and
// the main event loop they depend on are published by now.
SignalMainRuntimeReady(false /* failed */);
Expand Down
Expand Up
@@ -1327,6 +1370,9 @@ void Runtime::DestroyRuntime() {
s_current.id = -1;
JEnv::UnpinThreadEnv();
}
Runtime* self = this;
s_mainRuntime.compare_exchange_strong(self, nullptr,
std::memory_order_acq_rel);
// The events state holds v8::Global handles (backing event target, dispatch
// closures and tracked promise rejections) - reset them while the isolate
// is still alive.
Expand Down
Expand Up
@@ -1419,6 +1465,7 @@ bool Runtime::s_mainRuntimeFailed = false;
v8::Platform* Runtime::platform = nullptr;
int Runtime::m_androidVersion = Runtime::GetAndroidVersion();
std::shared_ptr<EventLoop> Runtime::s_mainEventLoop;
std::atomic<Runtime*> Runtime::s_mainRuntime{nullptr};
thread_local Runtime::CurrentRuntime Runtime::s_current;
thread_local PendingIsolateSetup Runtime::s_pendingIsolateSetup;
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -85,6 +85,14 @@ class Runtime {
*/
static void SetPendingIsolateSetup(PendingIsolateSetup setup);
/*
* Tears down the native runtime of a bootstrap that failed on the
* Java side after initNativeScript returned, such as a throwing
* ts_helpers.js. No-op when no native runtime is registered under the
* id, which is the case for a bootstrap that failed inside Init.
*/
static void UnwindFailedBootstrap(int runtimeId);
static Runtime* GetRuntime(int runtimeId);
static Runtime* GetRuntime(v8::Isolate* isolate);
Expand Down
Expand Up
@@ -233,6 +241,16 @@ class Runtime {
static std::shared_ptr<EventLoop> GetMainEventLoop() {
return s_mainEventLoop;
}
/*
* The main runtime, or null while there is none: before it finishes
* initializing and after it is destroyed. Its id is whatever its
* bootstrap attempt was handed, which is 0 only when the first attempt
* succeeded.
*/
static Runtime* GetMainRuntime() {
return s_mainRuntime.load(std::memory_order_acquire);
}
static JavaVM* GetJVM() {
return s_jvm;
}
Expand Down
Expand Up
@@ -390,7 +408,7 @@ class Runtime {
v8::Persistent<v8::Function>* m_gcFunc;
volatile bool m_runGC;
v8::Persistent<v8::Context>* m_context;
v8::Persistent<v8::Context>* m_context = nullptr ;
// Decided by ElectMainRuntime, before anything can read it.
bool m_isMainThread = false;
Expand Down
Expand Up
@@ -422,10 +440,11 @@ class Runtime {
static void SignalMainRuntimeReady(bool failed);
/*
* Unwinds an initialization that threw after the isolate existed. The
* Java-side rollback only unwinds Java state, which would otherwise
* leave the isolate in the runtime caches and the half-built Runtime
* holding everything it had allocated.
* Unwinds an initialization that threw after the isolate existed, or
* one that finished and then failed on the Java side. The Java-side
* rollback only unwinds Java state, which would otherwise leave the
* isolate in the runtime caches and the Runtime holding everything it
* had allocated.
*/
void UnwindFailedInit();
jobject ConvertJsValueToJavaObject(JEnv& env, const v8::Local<v8::Value>& value, int classReturnType);
Expand Down
Expand Up
@@ -460,6 +479,7 @@ class Runtime {
static bool s_mainRuntimeFailed;
static std::shared_ptr<EventLoop> s_mainEventLoop;
static std::atomic<Runtime*> s_mainRuntime;
// One struct because without ELF TLS (minSdk < 29) every thread_local costs a lookup.
struct CurrentRuntime {
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -115,6 +115,22 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_initNativeScript(JNIEnv* _env, jo
}
}
extern "C" JNIEXPORT void Java_com_tns_Runtime_unwindFailedBootstrap(JNIEnv* _env, jclass clazz, jint runtimeId) {
try {
Runtime::UnwindFailedBootstrap(runtimeId);
} catch (NativeScriptException& e) {
e.ReThrowToJava();
} catch (std::exception e) {
stringstream ss;
ss << "Error: c++ exception: " << e.what() << endl;
NativeScriptException nsEx(ss.str());
nsEx.ReThrowToJava();
} catch (...) {
NativeScriptException nsEx(std::string("Error: c++ exception!"));
nsEx.ReThrowToJava();
}
}
Runtime* TryGetRuntime(int runtimeId) {
Runtime* runtime = nullptr;
try {
Expand Down
Original file line number
Diff line number
Diff line change
Expand Up
@@ -42,6 +42,8 @@ private native void initNativeScript(int runtimeId, String filesPath, String nat
private native Object runScript(int runtimeId, String filePath) throws NativeScriptException;
private static native void unwindFailedBootstrap(int runtimeId);
private native Object callJSMethodNative(int runtimeId, Object javaObject, int javaObjectID, String methodName, int methodNameId, int retType, boolean isConstructor, int argCount, long[] argValues, Object[] argRefs, long[] primitiveResult) throws NativeScriptException;
private native void createJSInstanceNative(int runtimeId, Object javaObject, int javaObjectID, String canonicalName);
Expand Down
Expand Up
@@ -718,6 +720,15 @@ private static Runtime initRuntime(DynamicConfiguration dynamicConfiguration) {
indexRuntime(runtime.getRuntimeId(), null);
currentRuntime.remove();
GcListener.unsubscribe(runtime);
// ts_helpers.js runs after the native runtime is fully built, so a
// failure there leaves the isolate behind unless it is torn down
// here as well; after the unsubscribe, so no GC notification can
// still be running against it
try {
unwindFailedBootstrap(runtime.getRuntimeId());
} catch (Throwable unwindError) {
t.addSuppressed(unwindError);
}
throw t;
}
Expand Down
Loading
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.