Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion test-app/runtime/src/main/cpp/CallbackHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,10 +741,16 @@ void CallbackHandlers::RunOnMainThreadCallback(const FunctionCallbackInfo<v8::Va
return;
}

std::shared_ptr<IsolateGate> gate;
if (auto wrapper = WorkerWrapper::FromIsolate(isolate)) {
gate = wrapper->Gate();
}

{
std::lock_guard<std::mutex> lock(cacheMutex_);
bool inserted;
std::tie(std::ignore, inserted) = cache_.try_emplace(key, isolate, callback);
std::tie(std::ignore, inserted) = cache_.try_emplace(key, isolate, callback,
std::move(gate));
NS_DCHECK(inserted && "Main thread callback ID should not be duplicated");
}

Expand All @@ -756,16 +762,31 @@ void CallbackHandlers::RunOnMainThreadCallback(const FunctionCallbackInfo<v8::Va

void CallbackHandlers::RunMainThreadEntry(uint64_t key) {
Isolate *isolate;
std::shared_ptr<IsolateGate> gate;
{
std::lock_guard<std::mutex> lock(cacheMutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return;
}
isolate = it->second.isolate_;
gate = it->second.gate_;
}

// A worker that has started shutting down may dispose its isolate at any
// moment, so its callback is dropped without touching the isolate.
std::unique_lock<std::mutex> gateLock;
if (gate != nullptr) {
gateLock = std::unique_lock<std::mutex>(gate->mutex);
if (!gate->open) {
return;
}
}

v8::Locker locker(isolate);
if (gateLock.owns_lock()) {
gateLock.unlock();
}
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<v8::Function> cb;
Expand All @@ -788,6 +809,14 @@ void CallbackHandlers::RunMainThreadEntry(uint64_t key) {

if (tc.HasCaught() &&
!NativeScriptException::ContainUncaughtCallbackException(isolate, tc)) {
if (gate != nullptr) {
// A worker's error belongs to the worker, as if the callback had
// run on its own thread: its onerror, then the parent's Worker
// object. Thrown from here it would become a Java exception on the
// main thread.
CallWorkerScopeOnErrorHandle(isolate, tc);
return;
}
// surfaces via the event loop's guard as a pending Java exception
throw NativeScriptException(tc);
}
Expand Down
13 changes: 11 additions & 2 deletions test-app/runtime/src/main/cpp/CallbackHandlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
#include "NativeScriptAssert.h"
#include "NativeScriptException.h"
#include "Runtime.h"
#include <memory>

namespace tns {
struct IsolateGate;
}

namespace tns {
class CallbackHandlers {
Expand Down Expand Up @@ -218,9 +223,11 @@ namespace tns {


struct CacheEntry {
CacheEntry(v8::Isolate* isolate, v8::Local<v8::Function> callback)
CacheEntry(v8::Isolate* isolate, v8::Local<v8::Function> callback,
std::shared_ptr<IsolateGate> gate)
: isolate_(isolate),
callback_(isolate, callback) {
callback_(isolate, callback),
gate_(std::move(gate)) {
}

~CacheEntry() {
Expand All @@ -229,6 +236,8 @@ namespace tns {

v8::Isolate* isolate_;
v8::Global<v8::Function> callback_;
// null for the main isolate, which is never disposed
std::shared_ptr<IsolateGate> gate_;
};

static robin_hood::unordered_map<uint64_t, CacheEntry> cache_;
Expand Down
4 changes: 1 addition & 3 deletions test-app/runtime/src/main/cpp/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1338,9 +1338,7 @@ void Runtime::DestroyRuntime() {
m_dispatchNativeUncaughtErrorFunc.Reset();
// Both hold v8::Global handles to JS callbacks, so their entries must be
// dropped here rather than in ~Runtime, which runs after Isolate::Dispose --
// resetting a Global then writes into a freed handle table. Doing it here
// also closes a window in which the main thread could take a Locker on this
// isolate (RunMainThreadEntry) after it had already been disposed.
// resetting a Global then writes into a freed handle table.
CallbackHandlers::RemoveIsolateEntries(m_isolate);
FrameCallbacks::RemoveIsolateEntries(m_isolate);

Expand Down
6 changes: 6 additions & 0 deletions test-app/runtime/src/main/cpp/WorkerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,12 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr<WorkerWrapper> self) {
}
}

// No Locker is held here, as the gate requires.
{
std::lock_guard<std::mutex> lock(gate_->mutex);
gate_->open = false;
}

// Terminate any workers this worker created (nested workers). Their
// Worker object persistents live in this isolate, so they must be
// released before the isolate is disposed below. Each child cascades to
Expand Down
17 changes: 17 additions & 0 deletions test-app/runtime/src/main/cpp/WorkerWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ class Runtime;
class WorkerInspectorClient;
#endif

/*
* Lets a thread other than the worker's own take the worker's Locker without
* racing its disposal. A caller holds `mutex`, gives up if `open` is false,
* and releases `mutex` only once it owns the Locker. The worker's shutdown
* closes the gate before it takes its teardown Locker, so a closed gate means
* the isolate may already be gone.
*
* Never take `mutex` while holding the worker's Locker: a gated caller waits
* for that Locker with `mutex` held.
*/
struct IsolateGate {
std::mutex mutex;
bool open = true;
};

/*
* Owns a worker's native thread and its lifecycle, mirroring the iOS
* runtime's WorkerWrapper. The thread is a std::thread that attaches to the
Expand All @@ -53,6 +68,7 @@ class WorkerWrapper : public std::enable_shared_from_this<WorkerWrapper> {
bool IsTerminating() const { return isTerminating_; }
bool IsClosing() const { return isClosing_; }
bool IsDisposed() const { return isDisposed_; }
const std::shared_ptr<IsolateGate>& Gate() const { return gate_; }

/*
* Spawns the (detached) worker thread. The thread holds a shared_ptr to
Expand Down Expand Up @@ -224,6 +240,7 @@ class WorkerWrapper : public std::enable_shared_from_this<WorkerWrapper> {
std::atomic_bool isClosing_;
std::atomic_bool isTerminating_;
std::atomic_bool isDisposed_;
const std::shared_ptr<IsolateGate> gate_ = std::make_shared<IsolateGate>();
// False until the entry script has finished evaluating
// (EnableMessageQueue); DrainPendingTasks leaves the queue untouched while
// disabled. Written and read on the worker thread only - the atomic is
Expand Down
Loading