Skip to content

Commit cee3713

Browse files
authored
fix(runtime): keep the V8 platform and main Runtime alive through exit() (#469)
* fix(runtime): keep the V8 platform and main Runtime alive through exit() Both were file-scope globals with non-trivial destructors, so exit() tore them down from whatever thread called it. iOS reclaims a suspended app by calling exit() on a BackBoard thread while the main thread and every worker are still running; the platform was freed first, then ~Runtime terminated the workers, and a worker disposing its isolate dereferenced the freed platform in V8's TracingCpuProfilerImpl destructor. The same pass also locked the live main isolate and deallocated JS-owned ObjC objects on a foreign thread. The platform is now a raw pointer that is never deleted; V8 holds it by raw pointer for every isolate anyway. The main Runtime is a raw pointer deleted only by shutdownRuntime and before re-initialization, so embedders keep the deliberate teardown and restart paths unchanged. * fix(runtime): clear the main runtime pointer before destroying it Matches the order the previous unique_ptr reset had, so a caller checking runtime_ during shutdownRuntime sees null rather than a runtime whose destructor is running. * docs(runtime): describe runtime lifetime without the removed unique_ptr
1 parent 5343e23 commit cee3713

3 files changed

Lines changed: 31 additions & 28 deletions

File tree

‎NativeScript/NativeScript.mm‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,18 @@ - (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop {
3939
tns::Tasks::Drain();
4040
}
4141

42-
std::unique_ptr<Runtime> runtime_;
42+
// A raw pointer so that exit() never runs ~Runtime from a static destructor:
43+
// exit() can come from any thread while the main thread and every worker are
44+
// still live, and a teardown then races them and the other static destructors.
45+
// A dying process needs no teardown; shutdownRuntime is the deliberate path.
46+
Runtime* runtime_ = nullptr;
47+
48+
// Cleared before ~Runtime runs so no caller sees a runtime mid-destruction.
49+
static void DestroyMainRuntime() {
50+
Runtime* runtime = runtime_;
51+
runtime_ = nullptr;
52+
delete runtime;
53+
}
4354

4455
- (void)runMainApplication {
4556
runtime_->RunMainScript();
@@ -141,9 +152,7 @@ - (void)shutdownRuntime {
141152
Console::DetachInspectorClient();
142153
}
143154
tns::Tasks::ClearTasks();
144-
if (runtime_ != nullptr) {
145-
runtime_ = nullptr;
146-
}
155+
DestroyMainRuntime();
147156
}
148157

149158
- (instancetype)initializeWithConfig:(Config*)config {
@@ -165,8 +174,8 @@ - (instancetype)initializeWithConfig:(Config*)config {
165174
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
166175

167176
Runtime::Initialize();
168-
runtime_ = nullptr;
169-
runtime_ = std::make_unique<Runtime>();
177+
DestroyMainRuntime();
178+
runtime_ = new Runtime();
170179

171180
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
172181
Isolate* isolate = runtime_->CreateIsolate();
@@ -181,7 +190,7 @@ - (instancetype)initializeWithConfig:(Config*)config {
181190
Isolate::Scope isolate_scope(isolate);
182191
HandleScope handle_scope(isolate);
183192
v8_inspector::JsV8InspectorClient* inspectorClient =
184-
new v8_inspector::JsV8InspectorClient(runtime_.get());
193+
new v8_inspector::JsV8InspectorClient(runtime_);
185194
inspectorClient->init();
186195
inspectorClient->registerModules();
187196
inspectorClient->connect([config ArgumentsCount], [config Arguments]);

‎NativeScript/runtime/Runtime.h‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class Runtime {
100100
return currentRuntime_->IsRuntimeWorker();
101101
}
102102

103-
static std::shared_ptr<v8::Platform> GetPlatform() { return platform_; }
103+
static v8::Platform* GetPlatform() { return platform_; }
104104

105105
static id GetAppConfigValue(std::string key);
106106

@@ -119,9 +119,6 @@ class Runtime {
119119
static napi_env GetNapiEnvIfAlive(const Runtime* runtime);
120120

121121
// Milliseconds since this runtime's time origin, on the monotonic clock.
122-
// Not inline on purpose: an inline definition would have to reach the
123-
// platform through GetPlatform(), which copies a shared_ptr on every call,
124-
// while the out-of-line definition reads platform_ directly.
125122
double PerformanceNowMillis();
126123

127124
// Wall-clock milliseconds since the Unix epoch at the moment the time origin
@@ -138,7 +135,10 @@ class Runtime {
138135

139136
private:
140137
static thread_local Runtime* currentRuntime_;
141-
static std::shared_ptr<v8::Platform> platform_;
138+
// Lives until the process dies and is never deleted: V8 holds it by raw
139+
// pointer for every isolate, and exit() runs static destructors while the
140+
// main thread and worker threads are still tearing isolates down.
141+
static v8::Platform* platform_;
142142
static std::vector<v8::Isolate*> isolates_;
143143
static SpinMutex isolatesMutex_;
144144
static bool v8Initialized_;
@@ -171,13 +171,9 @@ class Runtime {
171171
CFRunLoopObserverRef rejectionObserver_ = nullptr;
172172
double timeOriginMonotonic_;
173173
double timeOriginRealtimeMs_;
174-
// TODO: refactor this. This is only needed because, during program
175-
// termination (UIApplicationMain not called) the Cache::Workers is released
176-
// (static initialization order fiasco
177-
// https://en.cppreference.com/w/cpp/language/siof) so it released the
178-
// Cache::Workers shared_ptr and then releases the Runtime unique_ptr
179-
// eventually we just need to refactor so that Runtime::Initialize is
180-
// responsible for its initalization and lifecycle
174+
// Keeps Caches::Workers alive for as long as this runtime exists: ~Runtime
175+
// reads it, and a worker runtime can be deleted on its own thread while
176+
// exit() is already running static destructors on another.
181177
std::shared_ptr<ConcurrentMap<int, std::shared_ptr<Caches::WorkerState>>>
182178
workerCache_;
183179
};

‎NativeScript/runtime/Runtime.mm‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,10 @@ static void InitializeImportMetaObject(Local<Context> context, Local<Module> mod
193193
// kCFTimeZoneSystemTimeZoneDidChangeNotification, NULL);
194194

195195
void DisposeIsolateWhenPossible(Isolate* isolate) {
196-
// most of the time, this will never delay disposal
197-
// occasionally this can happen when the runtime is destroyed by actions of its own isolate
198-
// as an example: isolate calls exit(0), which in turn destroys the Runtime unique_ptr
199-
// another scenario is when embedding nativescript, if the embedder deletes the runtime as a
200-
// result of a callback from JS in the case of exit(0), the app will die before actually disposing
201-
// the isolate, which isn't a problem
196+
// Disposal is deferred only while the isolate is still entered, which happens
197+
// when the runtime is deleted by code running inside its own isolate: an
198+
// embedder calling shutdownRuntime from a JS callback. exit() never deletes a
199+
// runtime, so a process that is dying leaves its isolates to the OS.
202200
if (isolate->IsInUse()) {
203201
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_MSEC)),
204202
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
@@ -331,9 +329,9 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
331329

332330
// wrap the default platform so foreground tasks ride each runtime
333331
// thread's CFRunLoop instead of sitting in never-pumped libplatform queues
334-
Runtime::platform_ = std::make_shared<NativeScriptPlatform>(platform::NewDefaultPlatform());
332+
Runtime::platform_ = new NativeScriptPlatform(platform::NewDefaultPlatform());
335333

336-
V8::InitializePlatform(Runtime::platform_.get());
334+
V8::InitializePlatform(Runtime::platform_);
337335
V8::Initialize();
338336
v8Initialized_ = true;
339337
}
@@ -781,7 +779,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
781779
return nullptr;
782780
}
783781

784-
std::shared_ptr<Platform> Runtime::platform_;
782+
Platform* Runtime::platform_ = nullptr;
785783
std::vector<Isolate*> Runtime::isolates_;
786784
bool Runtime::v8Initialized_ = false;
787785
thread_local Runtime* Runtime::currentRuntime_ = nullptr;

0 commit comments

Comments
 (0)