#include #include "DataWrapper.h" #include "Helpers.h" #include "Runtime.h" #include "Caches.h" #include "Constants.h" using namespace v8; namespace tns { static NSOperationQueue* workers_ = nil; __attribute__((constructor)) void staticInitMethod() { workers_ = [[NSOperationQueue alloc] init]; workers_.maxConcurrentOperationCount = 100; } WorkerWrapper::WorkerWrapper(v8::Isolate* mainIsolate, std::function thiz, std::string)> onMessage) : mainIsolate_(mainIsolate), workerIsolate_(nullptr), isRunning_(false), isClosing_(false), isTerminating_(false), isDisposed_(false), isWeak_(false), onMessage_(onMessage) { } const WrapperType WorkerWrapper::Type() { return WrapperType::Worker; } const int WorkerWrapper::Id() { return this->workerId_; } const bool WorkerWrapper::IsRunning() { return this->isRunning_; } const bool WorkerWrapper::IsClosing() { return this->isClosing_; } const int WorkerWrapper::WorkerId() { return this->workerId_; } void WorkerWrapper::PostMessage(std::string message) { if (!this->isTerminating_) { this->queue_.Push(message); } } void WorkerWrapper::Start(std::shared_ptr> poWorker, std::function func) { this->poWorker_ = poWorker; this->workerId_ = nextId_.fetch_add(1, std::memory_order_relaxed) + 1; [workers_ addOperationWithBlock:^{ this->BackgroundLooper(func); }]; this->isRunning_ = true; } void WorkerWrapper::DrainPendingTasks() { std::vector messages = this->queue_.PopAll(); v8::Locker locker(this->workerIsolate_); Isolate::Scope isolate_scope(this->workerIsolate_); HandleScope handle_scope(this->workerIsolate_); Local context = Caches::Get(this->workerIsolate_)->GetContext(); Local global = context->Global(); for (std::string message: messages) { if (this->isTerminating_) { break; } TryCatch tc(this->workerIsolate_); this->onMessage_(this->workerIsolate_, global, message); if (tc.HasCaught()) { this->CallOnErrorHandlers(tc); } } } void WorkerWrapper::BackgroundLooper(std::function func) { if (!this->isTerminating_) { CFRunLoopRef runLoop = CFRunLoopGetCurrent(); this->queue_.Initialize(runLoop, [](void* info) { WorkerWrapper* w = static_cast(info); w->DrainPendingTasks(); }, this); this->workerIsolate_ = func(); this->DrainPendingTasks(); // check again as it could terminate before this if (!this->isTerminating_) { CFRunLoopRun(); } } this->isDisposed_ = true; Runtime* runtime = Runtime::GetCurrentRuntime(); delete runtime; } void WorkerWrapper::Close() { this->isClosing_ = true; } void WorkerWrapper::Terminate() { // set terminating to true atomically bool wasTerminating = this->isTerminating_.exchange(true); if (!wasTerminating) { if (this->workerIsolate_ != nullptr) { this->workerIsolate_->TerminateExecution(); } this->queue_.Terminate(); this->isRunning_ = false; } } void WorkerWrapper::CallOnErrorHandlers(TryCatch& tc) { if (this->isTerminating_) { return; } Local context = Caches::Get(this->workerIsolate_)->GetContext(); Local global = context->Global(); Local onErrorVal; bool success = global->Get(context, tns::ToV8String(this->workerIsolate_, "onerror")).ToLocal(&onErrorVal); Isolate* isolate = context->GetIsolate(); tns::Assert(success, isolate); if (!onErrorVal.IsEmpty() && onErrorVal->IsFunction()) { Local onErrorFunc = onErrorVal.As(); Local error = tc.Exception(); Local args[1] = { error }; Local result; TryCatch innerTc(this->workerIsolate_); success = onErrorFunc->Call(context, v8::Undefined(this->workerIsolate_), 1, args).ToLocal(&result); if (success && !result.IsEmpty() && result->BooleanValue(this->workerIsolate_)) { // Do nothing, exception is handled and does not need to be raised to the main thread's onerror handler return; } if (!success && innerTc.HasCaught()) { this->PassUncaughtExceptionFromWorkerToMain(context, innerTc); } this->PassUncaughtExceptionFromWorkerToMain(context, tc); } } void WorkerWrapper::PassUncaughtExceptionFromWorkerToMain(Local context, TryCatch& tc, bool async) { Isolate* workerIsolate = context->GetIsolate(); int lineNumber; bool success = tc.Message()->GetLineNumber(context).To(&lineNumber); Isolate* isolate = context->GetIsolate(); tns::Assert(success, isolate); std::string message = tns::ToString(workerIsolate, tc.Message()->Get()); Local source; success = tc.Message()->GetScriptResourceName()->ToString(context).ToLocal(&source); tns::Assert(success, isolate); std::string src = tns::ToString(workerIsolate, source); std::string stackTrace = ""; Local stackTraceVal = tc.StackTrace(context).FromMaybe(Local()); if (!stackTraceVal.IsEmpty()) { Local stackTraceStr = stackTraceVal->ToDetailString(context).FromMaybe(Local()); if (!stackTraceStr.IsEmpty()) { stackTrace = tns::ToString(workerIsolate, stackTraceStr); } } auto runtime = static_cast(mainIsolate_->GetData(Constants::RUNTIME_SLOT)); if (runtime == nullptr) { return; } tns::ExecuteOnRunLoop(runtime->RuntimeLoop(), [this, message, src, stackTrace, lineNumber]() { v8::Locker locker(this->mainIsolate_); Isolate::Scope isolate_scope(this->mainIsolate_); HandleScope handle_scope(this->mainIsolate_); Local worker = this->poWorker_->Get(this->mainIsolate_).As(); Local context = Caches::Get(this->mainIsolate_)->GetContext(); Local onErrorVal; bool success = worker->Get(context, tns::ToV8String(this->mainIsolate_, "onerror")).ToLocal(&onErrorVal); tns::Assert(success, this->mainIsolate_); if (!onErrorVal.IsEmpty() && onErrorVal->IsFunction()) { Local onErrorFunc = onErrorVal.As(); Local arg = this->ConstructErrorObject(context, message, src, stackTrace, lineNumber); Local args[1] = { arg }; Local result; TryCatch tc(this->mainIsolate_); bool success = onErrorFunc->Call(context, v8::Undefined(this->mainIsolate_), 1, args).ToLocal(&result); if (!success && tc.HasCaught()) { Local error = tc.Exception(); Log(@"%s", tns::ToString(this->mainIsolate_, error).c_str()); this->mainIsolate_->ThrowException(error); } } }, async); } Local WorkerWrapper::ConstructErrorObject(Local context, std::string message, std::string source, std::string stackTrace, int lineNumber) { Isolate* isolate = context->GetIsolate(); Local objTemplate = ObjectTemplate::New(isolate); Local obj; bool success = objTemplate->NewInstance(context).ToLocal(&obj); tns::Assert(success, isolate); tns::Assert(obj->Set(context, tns::ToV8String(isolate, "message"), tns::ToV8String(isolate, message)).FromMaybe(false), isolate); tns::Assert(obj->Set(context, tns::ToV8String(isolate, "filename"), tns::ToV8String(isolate, source)).FromMaybe(false), isolate); tns::Assert(obj->Set(context, tns::ToV8String(isolate, "stackTrace"), tns::ToV8String(isolate, stackTrace)).FromMaybe(false), isolate); tns::Assert(obj->Set(context, tns::ToV8String(isolate, "lineno"), Number::New(isolate, lineNumber)).FromMaybe(false), isolate); return obj; } std::atomic WorkerWrapper::nextId_(0); }