Skip to content

Commit 857834d

Browse files
fix: let a failed main runtime bootstrap be retried
A main runtime whose initialization throws hands the election back so a later bootstrap can retry, but the retry crashed: the unwind reset two never-initialized Persistent pointers, the second election initialized V8 again, and in debug builds the inspector looked the main runtime up by id 0. The unwind also left the platform's event loop entry, a crash breadcrumb slot and BuildMetadata's buffers and directory handle behind, and a retry past the metadata step rebuilt the process-wide tree.
1 parent 0ffff1b commit 857834d

5 files changed

Lines changed: 57 additions & 16 deletions

File tree

‎test-app/runtime/src/main/cpp/JsV8InspectorClient.cpp‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,11 @@ JsV8InspectorClient* JsV8InspectorClient::GetInstance() {
909909
// handleMessageOnSocketThread also calls this from the socket thread, so a
910910
// concurrent first call is possible: construct, then publish with a CAS and
911911
// discard our copy if another thread won the race.
912-
auto* created = new JsV8InspectorClient(Runtime::GetRuntime(0)->GetIsolate());
912+
Runtime* mainRuntime = Runtime::GetMainRuntime();
913+
if (mainRuntime == nullptr) {
914+
throw NativeScriptException("Cannot create the inspector: the main runtime is not initialized");
915+
}
916+
auto* created = new JsV8InspectorClient(mainRuntime->GetIsolate());
913917
JsV8InspectorClient* expected = nullptr;
914918
if (!instance.compare_exchange_strong(expected, created, std::memory_order_acq_rel,
915919
std::memory_order_acquire)) {

‎test-app/runtime/src/main/cpp/MetadataNode.cpp‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,8 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
20422042
throw NativeScriptException(ss.str());
20432043
}
20442044
}
2045+
// Only opened to tell a missing folder from a missing file.
2046+
closedir(dir);
20452047

20462048
string nodesFile = baseDir + "/treeNodeStream.dat";
20472049
string namesFile = baseDir + "/treeStringsStream.dat";
@@ -2068,9 +2070,11 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
20682070
<< "-byte records. The metadata is truncated or corrupt.";
20692071
throw NativeScriptException(ss.str());
20702072
}
2071-
char* nodes = new char[lenNodes];
2073+
// Owned until the reader takes them, so a file that fails to open further
2074+
// down does not strand the buffers already read.
2075+
std::unique_ptr<char[]> nodes(new char[lenNodes]);
20722076
rewind(f);
2073-
fread(nodes, 1, lenNodes, f);
2077+
fread(nodes.get(), 1, lenNodes, f);
20742078
fclose(f);
20752079

20762080
const int _512KB = 524288;
@@ -2085,9 +2089,9 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
20852089
}
20862090
fseek(f, 0, SEEK_END);
20872091
int lenNames = ftell(f);
2088-
char* names = new char[lenNames + _512KB];
2092+
std::unique_ptr<char[]> names(new char[lenNames + _512KB]);
20892093
rewind(f);
2090-
fread(names, 1, lenNames, f);
2094+
fread(names.get(), 1, lenNames, f);
20912095
fclose(f);
20922096

20932097
f = fopen(valuesFile.c_str(), "rb");
@@ -2115,11 +2119,9 @@ void MetadataNode::BuildMetadata(const string& filesPath) {
21152119

21162120
DEBUG_WRITE("time=%ld", (millis2 - millis1));
21172121

2118-
BuildMetadata(lenNodes, reinterpret_cast<uint8_t*>(nodes), lenNames, reinterpret_cast<uint8_t*>(names), lenValues, reinterpret_cast<uint8_t*>(values));
2119-
2120-
delete[] nodes;
2121-
//delete[] names;
2122-
//delete[] values;
2122+
// The reader keeps the names and values buffers for the life of the
2123+
// process and only reads the nodes buffer while it builds the tree.
2124+
BuildMetadata(lenNodes, reinterpret_cast<uint8_t*>(nodes.get()), lenNames, reinterpret_cast<uint8_t*>(names.release()), lenValues, reinterpret_cast<uint8_t*>(values));
21232125
}
21242126

21252127
void MetadataNode::BuildMetadata(uint32_t nodesLength, uint8_t* nodeData, uint32_t nameLength, uint8_t* nameData, uint32_t valueLength, uint8_t* valueData) {

‎test-app/runtime/src/main/cpp/ObjectManager.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class ObjectManager {
260260

261261
static jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID;
262262

263-
v8::Persistent<v8::Function>* m_poJsWrapperFunc;
263+
v8::Persistent<v8::Function>* m_poJsWrapperFunc = nullptr;
264264
};
265265
} // namespace tns
266266

‎test-app/runtime/src/main/cpp/Runtime.cpp‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ Runtime::~Runtime() {
313313
s_isolate2RuntimesCache.erase(it);
314314
}
315315
}
316+
// Same backstop for the breadcrumb slot Init took: the table is small and
317+
// fixed, so slots lost to failed bootstraps would crowd out live runtimes.
318+
CrashBreadcrumbs::UnregisterRuntime(m_id);
316319

317320
delete this->m_objectManager;
318321
// idempotent backstop for the matched erase WorkerWrapper does right after
@@ -712,9 +715,11 @@ void Runtime::ElectMainRuntime() {
712715
s_mainRuntimeElected = true;
713716
s_mainRuntimeFailed = false;
714717
m_isMainThread = true;
715-
// Once per process: V8::Initialize freezes the flag list, and setting a
716-
// flag afterwards aborts.
717-
InitializeV8();
718+
// Once per process, not once per election: a main runtime that failed
719+
// hands the election back, and V8 aborts both on a second
720+
// InitializePlatform and on a flag set after V8::Initialize froze the list.
721+
static std::once_flag v8Initialized;
722+
std::call_once(v8Initialized, InitializeV8);
718723
return;
719724
}
720725

@@ -760,6 +765,12 @@ void Runtime::UnwindFailedInit() {
760765
DestroyRuntime();
761766
}
762767
m_isolate->Dispose();
768+
// The ~Runtime backstop keys on m_isolate, which is cleared below, so the
769+
// platform's loop entry has to go here. Left behind, it would hand the
770+
// stopped loop to the next isolate allocated at this address.
771+
if (m_eventLoop != nullptr) {
772+
NativeScriptPlatform::Instance()->IsolateDisposed(m_isolate, m_eventLoop);
773+
}
763774
m_isolate = nullptr;
764775
}
765776

@@ -1071,7 +1082,15 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
10711082
// Do not build metadata (which should be static for the process) for non-main
10721083
// threads
10731084
if (m_isMainThread) {
1074-
MetadataNode::BuildMetadata(filesPath);
1085+
// Once per process, like V8 itself: the tree is process-wide state that
1086+
// outlives the runtime that built it, so a main runtime elected after an
1087+
// earlier one failed past this point reads the tree already there. Only
1088+
// the elected main runtime gets here, one at a time.
1089+
static bool metadataBuilt = false;
1090+
if (!metadataBuilt) {
1091+
MetadataNode::BuildMetadata(filesPath);
1092+
metadataBuilt = true;
1093+
}
10751094
}
10761095

10771096
auto enableProfiler = !profilerOutputDir.empty();
@@ -1089,6 +1108,7 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
10891108
s_currentRuntime = this;
10901109

10911110
if (m_isMainThread) {
1111+
s_mainRuntime.store(this, std::memory_order_release);
10921112
// Releases any runtime waiting in ElectMainRuntime: the metadata tree and
10931113
// the main event loop they depend on are published by now.
10941114
SignalMainRuntimeReady(false /* failed */);
@@ -1164,6 +1184,9 @@ void Runtime::DestroyRuntime() {
11641184
if (s_currentRuntime == this) {
11651185
s_currentRuntime = nullptr;
11661186
}
1187+
Runtime* self = this;
1188+
s_mainRuntime.compare_exchange_strong(self, nullptr,
1189+
std::memory_order_acq_rel);
11671190
// The events state holds v8::Global handles (backing event target, dispatch
11681191
// closures and tracked promise rejections) - reset them while the isolate
11691192
// is still alive.
@@ -1251,6 +1274,7 @@ bool Runtime::s_mainRuntimeFailed = false;
12511274
v8::Platform* Runtime::platform = nullptr;
12521275
int Runtime::m_androidVersion = Runtime::GetAndroidVersion();
12531276
std::shared_ptr<EventLoop> Runtime::s_mainEventLoop;
1277+
std::atomic<Runtime*> Runtime::s_mainRuntime{nullptr};
12541278

12551279
thread_local Runtime* Runtime::s_currentRuntime = nullptr;
12561280
thread_local PendingIsolateSetup Runtime::s_pendingIsolateSetup;

‎test-app/runtime/src/main/cpp/Runtime.h‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ class Runtime {
202202
static std::shared_ptr<EventLoop> GetMainEventLoop() {
203203
return s_mainEventLoop;
204204
}
205+
206+
/*
207+
* The main runtime, or null while there is none: before it finishes
208+
* initializing and after it is destroyed. Its id is whatever its
209+
* bootstrap attempt was handed, which is 0 only when the first attempt
210+
* succeeded.
211+
*/
212+
static Runtime* GetMainRuntime() {
213+
return s_mainRuntime.load(std::memory_order_acquire);
214+
}
205215
static JavaVM* GetJVM() {
206216
return s_jvm;
207217
}
@@ -351,7 +361,7 @@ class Runtime {
351361
v8::Persistent<v8::Function>* m_gcFunc;
352362
volatile bool m_runGC;
353363

354-
v8::Persistent<v8::Context>* m_context;
364+
v8::Persistent<v8::Context>* m_context = nullptr;
355365

356366
// Decided by ElectMainRuntime, before anything can read it.
357367
bool m_isMainThread = false;
@@ -422,6 +432,7 @@ class Runtime {
422432
static bool s_mainRuntimeFailed;
423433

424434
static std::shared_ptr<EventLoop> s_mainEventLoop;
435+
static std::atomic<Runtime*> s_mainRuntime;
425436

426437
static thread_local Runtime* s_currentRuntime;
427438
static thread_local PendingIsolateSetup s_pendingIsolateSetup;

0 commit comments

Comments
 (0)