@@ -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
@@ -735,16 +740,39 @@ void Runtime::SignalMainRuntimeReady(bool failed) {
735740 {
736741 std::lock_guard<std::mutex> lock (s_mainInitMutex);
737742 if (failed) {
738- // Hand the election back so a later bootstrap can retry.
743+ // Hand the election back so a later bootstrap can retry. A main runtime
744+ // that already signalled readiness and failed afterwards withdraws it
745+ // too, so nothing waiting on the next main runtime starts against this
746+ // one.
739747 s_mainRuntimeElected = false ;
740748 s_mainRuntimeFailed = true ;
749+ s_mainThreadInitialized.store (false , std::memory_order_release);
741750 } else {
742751 s_mainThreadInitialized.store (true , std::memory_order_release);
743752 }
744753 }
745754 s_mainInitReady.notify_all ();
746755}
747756
757+ void Runtime::UnwindFailedBootstrap (int runtimeId) {
758+ Runtime* runtime = nullptr ;
759+ {
760+ std::lock_guard<std::mutex> lock (s_runtimeCacheMutex);
761+ auto it = s_id2RuntimeCache.find (runtimeId);
762+ if (it != s_id2RuntimeCache.end ()) {
763+ runtime = it->second ;
764+ }
765+ }
766+ if (runtime == nullptr ) {
767+ return ;
768+ }
769+ // Only the bootstrapping thread can reach this runtime: no application JS
770+ // has run on it, so nothing has handed it to another thread or started a
771+ // worker from it.
772+ runtime->UnwindFailedInit ();
773+ delete runtime;
774+ }
775+
748776void Runtime::UnwindFailedInit () {
749777 /*
750778 * Reuses the two teardown windows rather than adding a third cleanup path.
@@ -760,6 +788,12 @@ void Runtime::UnwindFailedInit() {
760788 DestroyRuntime ();
761789 }
762790 m_isolate->Dispose ();
791+ // The ~Runtime backstop keys on m_isolate, which is cleared below, so the
792+ // platform's loop entry has to go here. Left behind, it would hand the
793+ // stopped loop to the next isolate allocated at this address.
794+ if (m_eventLoop != nullptr ) {
795+ NativeScriptPlatform::Instance ()->IsolateDisposed (m_isolate, m_eventLoop);
796+ }
763797 m_isolate = nullptr ;
764798 }
765799
@@ -1071,7 +1105,15 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
10711105 // Do not build metadata (which should be static for the process) for non-main
10721106 // threads
10731107 if (m_isMainThread) {
1074- MetadataNode::BuildMetadata (filesPath);
1108+ // Once per process, like V8 itself: the tree is process-wide state that
1109+ // outlives the runtime that built it, so a main runtime elected after an
1110+ // earlier one failed past this point reads the tree already there. Only
1111+ // the elected main runtime gets here, one at a time.
1112+ static bool metadataBuilt = false ;
1113+ if (!metadataBuilt) {
1114+ MetadataNode::BuildMetadata (filesPath);
1115+ metadataBuilt = true ;
1116+ }
10751117 }
10761118
10771119 auto enableProfiler = !profilerOutputDir.empty ();
@@ -1089,6 +1131,7 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath,
10891131 s_currentRuntime = this ;
10901132
10911133 if (m_isMainThread) {
1134+ s_mainRuntime.store (this , std::memory_order_release);
10921135 // Releases any runtime waiting in ElectMainRuntime: the metadata tree and
10931136 // the main event loop they depend on are published by now.
10941137 SignalMainRuntimeReady (false /* failed */ );
@@ -1164,6 +1207,9 @@ void Runtime::DestroyRuntime() {
11641207 if (s_currentRuntime == this ) {
11651208 s_currentRuntime = nullptr ;
11661209 }
1210+ Runtime* self = this ;
1211+ s_mainRuntime.compare_exchange_strong (self, nullptr ,
1212+ std::memory_order_acq_rel);
11671213 // The events state holds v8::Global handles (backing event target, dispatch
11681214 // closures and tracked promise rejections) - reset them while the isolate
11691215 // is still alive.
@@ -1251,6 +1297,7 @@ bool Runtime::s_mainRuntimeFailed = false;
12511297v8::Platform* Runtime::platform = nullptr ;
12521298int Runtime::m_androidVersion = Runtime::GetAndroidVersion();
12531299std::shared_ptr<EventLoop> Runtime::s_mainEventLoop;
1300+ std::atomic<Runtime*> Runtime::s_mainRuntime{nullptr };
12541301
12551302thread_local Runtime* Runtime::s_currentRuntime = nullptr ;
12561303thread_local PendingIsolateSetup Runtime::s_pendingIsolateSetup;
0 commit comments