diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index b02e12a0..d8674990 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -306,6 +306,15 @@ { v8::Locker locker(isolate); + // Checked again with the isolate locked: a runtime being destroyed holds + // this lock while it removes its caches, so the check above can pass and + // the lock then be granted only once the context is gone. ~Runtime drops + // the isolate from the live registry before it takes the lock, which is + // what makes this second look reliable. + if (!data->isolateWrapper_.IsValid()) { + memset(retValue, 0, cif->rtype->size); + return; + } Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); std::shared_ptr cache = Caches::Get(isolate); diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index 572fba6c..304535be 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -47,22 +47,26 @@ if (wrapper->isolateWrapper_.IsValid()) { Isolate* isolate = wrapper->isolateWrapper_.Isolate(); v8::Locker locker(isolate); - Isolate::Scope isolate_scope(isolate); - HandleScope handle_scope(isolate); - Local callback = wrapper->callback_->Get(isolate); - if (!callback.IsEmpty() && callback->IsObject()) { - // The callback's slot is the cache's owner, so only a wrapper - // still sitting in it is ours to free. - if (tns::GetValue(isolate, callback) == blockWrapper) { - tns::DeleteValue(isolate, callback); - } else { - blockWrapper = nullptr; + // Re-checked under the lock: ~Runtime holds it while it tears the + // isolate's caches down, so the check above can predate that. + if (wrapper->isolateWrapper_.IsValid()) { + Isolate::Scope isolate_scope(isolate); + HandleScope handle_scope(isolate); + Local callback = wrapper->callback_->Get(isolate); + if (!callback.IsEmpty() && callback->IsObject()) { + // The callback's slot is the cache's owner, so only a wrapper + // still sitting in it is ours to free. + if (tns::GetValue(isolate, callback) == blockWrapper) { + tns::DeleteValue(isolate, callback); + } else { + blockWrapper = nullptr; + } } + // Unconditional: an already-detached callback still owns its + // node, and dropping the persistent without a reset would leave + // that node rooted forever. + wrapper->callback_->Reset(); } - // Unconditional: an already-detached callback still owns its - // node, and dropping the persistent without a reset would leave - // that node rooted forever. - wrapper->callback_->Reset(); } // Outside the isolate guard: once the isolate is gone the cache // slot is unreachable and nothing else can free the wrapper.