Skip to content

Commit 68e8a02

Browse files
committed
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 (BLACKOUT-V3-IOS-2Y). 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.
1 parent 5343e23 commit 68e8a02

3 files changed

Lines changed: 22 additions & 15 deletions

File tree

‎NativeScript/NativeScript.mm‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@ - (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+
static void DestroyMainRuntime() {
49+
delete runtime_;
50+
runtime_ = nullptr;
51+
}
4352

4453
- (void)runMainApplication {
4554
runtime_->RunMainScript();
@@ -141,9 +150,7 @@ - (void)shutdownRuntime {
141150
Console::DetachInspectorClient();
142151
}
143152
tns::Tasks::ClearTasks();
144-
if (runtime_ != nullptr) {
145-
runtime_ = nullptr;
146-
}
153+
DestroyMainRuntime();
147154
}
148155

149156
- (instancetype)initializeWithConfig:(Config*)config {
@@ -165,8 +172,8 @@ - (instancetype)initializeWithConfig:(Config*)config {
165172
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
166173

167174
Runtime::Initialize();
168-
runtime_ = nullptr;
169-
runtime_ = std::make_unique<Runtime>();
175+
DestroyMainRuntime();
176+
runtime_ = new Runtime();
170177

171178
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
172179
Isolate* isolate = runtime_->CreateIsolate();
@@ -181,7 +188,7 @@ - (instancetype)initializeWithConfig:(Config*)config {
181188
Isolate::Scope isolate_scope(isolate);
182189
HandleScope handle_scope(isolate);
183190
v8_inspector::JsV8InspectorClient* inspectorClient =
184-
new v8_inspector::JsV8InspectorClient(runtime_.get());
191+
new v8_inspector::JsV8InspectorClient(runtime_);
185192
inspectorClient->init();
186193
inspectorClient->registerModules();
187194
inspectorClient->connect([config ArgumentsCount], [config Arguments]);

‎NativeScript/runtime/Runtime.h‎

Lines changed: 5 additions & 5 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_;

‎NativeScript/runtime/Runtime.mm‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,9 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
331331

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

336-
V8::InitializePlatform(Runtime::platform_.get());
336+
V8::InitializePlatform(Runtime::platform_);
337337
V8::Initialize();
338338
v8Initialized_ = true;
339339
}
@@ -781,7 +781,7 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
781781
return nullptr;
782782
}
783783

784-
std::shared_ptr<Platform> Runtime::platform_;
784+
Platform* Runtime::platform_ = nullptr;
785785
std::vector<Isolate*> Runtime::isolates_;
786786
bool Runtime::v8Initialized_ = false;
787787
thread_local Runtime* Runtime::currentRuntime_ = nullptr;

0 commit comments

Comments
 (0)