Skip to content

Commit a9a5ea4

Browse files
fix(worker): keep the worker isolate alive while terminate() uses it
Terminate() read the worker isolate and then interrupted it with no lock, while the worker thread could clear the pointer and dispose that isolate in the same window: a worker that ends through its own close() while its parent calls terminate(), or a parent that terminates its children during its own shutdown. Terminate() now holds a mutex across the read and the use, and the worker thread takes it when it withdraws the isolate, before disposing it.
1 parent 9b12329 commit a9a5ea4

2 files changed

Lines changed: 26 additions & 11 deletions

File tree

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,23 @@ void WorkerWrapper::Terminate() {
152152
return;
153153
}
154154

155-
Isolate* isolate = workerIsolate_.load();
156-
if (isolate != nullptr) {
157-
// The only v8 call that is legal from another thread - interrupts any
158-
// JS currently running on the worker (e.g. a busy loop).
159-
isolate->TerminateExecution();
160-
// A pump parked with nothing queued runs no JS, so the interrupt
161-
// above never materializes for it - the loop's own flag ends it.
162-
auto loop = NativeScriptPlatform::Instance()->LookupEventLoop(isolate);
163-
if (loop != nullptr) {
164-
loop->NoteTerminationRequested();
155+
{
156+
// Held across the use, not just the read: the worker thread withdraws
157+
// the isolate under the same mutex before disposing it, so a terminate
158+
// that already read it finishes with it first, and a later one finds
159+
// null.
160+
std::lock_guard<std::mutex> lock(workerIsolateMutex_);
161+
Isolate* isolate = workerIsolate_.load();
162+
if (isolate != nullptr) {
163+
// The only v8 call that is legal from another thread - interrupts any
164+
// JS currently running on the worker (e.g. a busy loop).
165+
isolate->TerminateExecution();
166+
// A pump parked with nothing queued runs no JS, so the interrupt
167+
// above never materializes for it - the loop's own flag ends it.
168+
auto loop = NativeScriptPlatform::Instance()->LookupEventLoop(isolate);
169+
if (loop != nullptr) {
170+
loop->NoteTerminationRequested();
171+
}
165172
}
166173
}
167174

@@ -656,7 +663,11 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr<WorkerWrapper> self) {
656663
// bootstrap failed between initWorkerRuntime and the workerIsolate_
657664
// publish (e.g. a JNI error while resolving the looper), the atomic is
658665
// still null while the isolate very much needs disposing.
659-
workerIsolate_.store(nullptr);
666+
{
667+
// Waits out a Terminate() on another thread that is still using it.
668+
std::lock_guard<std::mutex> lock(workerIsolateMutex_);
669+
workerIsolate_.store(nullptr);
670+
}
660671
Isolate* isolate = runtime_->GetIsolate();
661672
{
662673
v8::Locker locker(isolate);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ class WorkerWrapper : public std::enable_shared_from_this<WorkerWrapper> {
178178
// The parent runtime's task queue; weak so a child outliving its parent
179179
// just drops its posts instead of touching a dead runtime.
180180
std::weak_ptr<EventLoop> parentTasks_;
181+
// Written by the worker thread only: published once the runtime is up,
182+
// withdrawn before the isolate is disposed. Any other thread reads and uses
183+
// it under workerIsolateMutex_.
181184
std::atomic<v8::Isolate*> workerIsolate_;
185+
std::mutex workerIsolateMutex_;
182186
Runtime* runtime_;
183187

184188
const int workerId_;

0 commit comments

Comments
 (0)