#include "worker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace v8; namespace { const uint32_t worker_data_slot = 0; struct TransferData; struct WorkerMessage { uint8_t* data; size_t size; TransferData* transferData; }; struct MessageQueue { std::vector messages; kinc_mutex_t messageMutex; kinc_event_t event; Global messageFunc; const char* label = "?"; }; struct WorkerMessagePort { MessageQueue workerMessages; MessageQueue ownerMessages; std::atomic isTerminated{false}; }; struct WorkerData { char fileName[256]; WorkerMessagePort* messagePort; }; struct OwnedWorker { kinc_thread_t* workerThread; WorkerMessagePort* workerMessagePort; }; struct ContextData { std::vector workers; }; struct IntervalFunction { Global function; double interval; double nextCallTime; int id; bool isTimeout; }; struct IntervalFunctions { std::vector functions; int latestId; }; struct TransferData { std::vector> sharedArrayBuffers; std::vector wasmModules; std::unique_ptr sharedValueDispatch; }; class SerializerDelegate : public ValueSerializer::Delegate { public: TransferData* transferData; SerializerDelegate(TransferData* data) : transferData(data) {} void ThrowDataCloneError(Local message) override { Isolate* isolate = Isolate::GetCurrent(); isolate->ThrowException(message); } Maybe GetSharedArrayBufferId(Isolate* isolate, Local shared_array_buffer) override { auto backingStore = shared_array_buffer->GetBackingStore(); uint32_t id = (uint32_t)transferData->sharedArrayBuffers.size(); transferData->sharedArrayBuffers.push_back(backingStore); return Just(id); } Maybe GetWasmModuleTransferId(Isolate* isolate, Local module) override { CompiledWasmModule compiled = module->GetCompiledModule(); uint32_t id = (uint32_t)transferData->wasmModules.size(); transferData->wasmModules.push_back(compiled); return Just(id); } bool AdoptSharedValueConveyor(Isolate* isolate, SharedValueConveyor&& conveyor) override { transferData->sharedValueDispatch = std::make_unique(std::move(conveyor)); return true; } }; class DeserializerDelegate : public ValueDeserializer::Delegate { public: TransferData* transferData; DeserializerDelegate(TransferData* data) : transferData(data) {} MaybeLocal GetSharedArrayBufferFromId(Isolate* isolate, uint32_t clone_id) override { if (clone_id >= transferData->sharedArrayBuffers.size()) { return MaybeLocal(); } return SharedArrayBuffer::New(isolate, transferData->sharedArrayBuffers[clone_id]); } MaybeLocal GetWasmModuleFromId(Isolate* isolate, uint32_t transfer_id) override { if (transfer_id >= transferData->wasmModules.size()) { return MaybeLocal(); } return WasmModuleObject::FromCompiledModule(isolate, transferData->wasmModules[transfer_id]); } const SharedValueConveyor* GetSharedValueConveyor(Isolate* isolate) override { return transferData->sharedValueDispatch.get(); } }; void handle_exception(Isolate* isolate, TryCatch* try_catch); void set_onmessage(MessageQueue* messageQueue, Isolate* isolate, Local callback) { if (callback->IsFunction()) { messageQueue->messageFunc.Reset(isolate, Local::Cast(callback)); } else if (callback->IsNullOrUndefined()) { messageQueue->messageFunc.Reset(); } else { kinc_log(KINC_LOG_LEVEL_ERROR, "[set_onmessage]: argument is neither a function nor null"); } } void post_message(MessageQueue* messageQueue, Isolate* isolate, Local messageObject) { Local context = isolate->GetCurrentContext(); TransferData* transferData = new TransferData(); SerializerDelegate delegate(transferData); ValueSerializer serializer(isolate, &delegate); serializer.WriteHeader(); TryCatch try_catch(isolate); Maybe writeResult = serializer.WriteValue(context, messageObject); if (writeResult.IsNothing()) { if (try_catch.HasCaught()) { isolate->ThrowException(try_catch.Exception()); } else { isolate->ThrowException(String::NewFromUtf8(isolate, "postMessage: failed to serialize message").ToLocalChecked()); } delete transferData; return; } std::pair released = serializer.Release(); WorkerMessage message; message.data = released.first; message.size = released.second; message.transferData = transferData; kinc_mutex_lock(&messageQueue->messageMutex); messageQueue->messages.push_back(message); kinc_mutex_unlock(&messageQueue->messageMutex); kinc_event_signal(&messageQueue->event); } void handle_message_queue(Isolate* isolate, const Global& context, MessageQueue* messageQueue) { kinc_mutex_lock(&messageQueue->messageMutex); if (messageQueue->messages.empty()) { kinc_mutex_unlock(&messageQueue->messageMutex); return; } std::vector messages; messages.swap(messageQueue->messages); kinc_mutex_unlock(&messageQueue->messageMutex); if (messageQueue->messageFunc.IsEmpty()) { for (WorkerMessage& message : messages) { free(message.data); delete message.transferData; } return; } HandleScope scope(isolate); Local current_context = Local::New(isolate, context); Context::Scope context_scope(current_context); for (WorkerMessage& message : messages) { DeserializerDelegate delegate(message.transferData); ValueDeserializer deserializer(isolate, message.data, message.size, &delegate); Maybe headerResult = deserializer.ReadHeader(current_context); if (headerResult.IsNothing()) { free(message.data); delete message.transferData; continue; } MaybeLocal value = deserializer.ReadValue(current_context); free(message.data); if (value.IsEmpty()) { delete message.transferData; continue; } Local object = Object::New(isolate); object->Set(current_context, String::NewFromUtf8(isolate, "data").ToLocalChecked(), value.ToLocalChecked()); Local args[] = { object }; Local message_func = messageQueue->messageFunc.Get(isolate); TryCatch try_catch(isolate); Local callResult; if (!message_func->Call(current_context, current_context->Global(), 1, args).ToLocal(&callResult)) { handle_exception(isolate, &try_catch); } delete message.transferData; } } void worker_console_log(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); std::string output; for (int i = 0; i < args.Length(); ++i) { if (i > 0) output += " "; String::Utf8Value utf8(isolate, args[i]); output += *utf8; } kinc_log(KINC_LOG_LEVEL_INFO, "%s", output.c_str()); } void handle_exception(Isolate* isolate, TryCatch* try_catch) { MaybeLocal trace = try_catch->StackTrace(isolate->GetCurrentContext()); if (trace.IsEmpty()) { MaybeLocal message = try_catch->Message(); if (message.IsEmpty()) { kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception (no message available)"); } else { v8::String::Utf8Value stack_trace(isolate, message.ToLocalChecked()->Get()); kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception %s", *stack_trace); } } else { v8::String::Utf8Value stack_trace(isolate, trace.ToLocalChecked()); kinc_log(KINC_LOG_LEVEL_ERROR, "uncaught exception %s", *stack_trace); } } void worker_post_message(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1) { return; } Local external = Local::Cast(args.Data()); WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value(); post_message(&messagePort->workerMessages, isolate, args[0]); } void worker_onmessage_get(Local property, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); Local external = Local::Cast(info.Data()); WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value(); info.GetReturnValue().Set(messagePort->ownerMessages.messageFunc); } void worker_onmessage_set(Local property, Local value, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); HandleScope scope(isolate); Local external = Local::Cast(info.Data()); WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value(); set_onmessage(&messagePort->ownerMessages, isolate, value); } void worker_add_event_listener(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 2) { return; } Local name = Local::Cast(args[0]); char event_name[256]; int length = name->WriteUtf8(isolate, event_name, 255); event_name[length] = 0; if (strcmp(event_name, "message") != 0) { kinc_log(KINC_LOG_LEVEL_WARNING, "Trying to add listener for unknown event %s", event_name); return; } Local external = Local::Cast(args.Data()); WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value(); set_onmessage(&messagePort->ownerMessages, isolate, args[1]); } int add_timer(IntervalFunctions* interval_functions, Isolate* isolate, const FunctionCallbackInfo& args, bool is_timeout) { if (args.Length() < 1 || !args[0]->IsFunction()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Timer callback must be a function").ToLocalChecked()); return -1; } Local function = Local::Cast(args[0]); double delay; if (args.Length() > 1) { if (!args[1]->IsNumber()) { isolate->ThrowException(String::NewFromUtf8(isolate, "Timer delay must be a number").ToLocalChecked()); return -1; } delay = Local::Cast(args[1])->Value() / 1000.0; } else { delay = is_timeout ? 0.0 : 0.016; } interval_functions->functions.emplace_back(); IntervalFunction& interval_function = interval_functions->functions.back(); interval_function.function.Reset(isolate, function); interval_function.interval = delay; interval_function.nextCallTime = kinc_time() + delay; interval_function.id = interval_functions->latestId; interval_function.isTimeout = is_timeout; interval_functions->latestId++; return interval_function.id; } void worker_set_interval(const FunctionCallbackInfo& args) { HandleScope scope(args.GetIsolate()); IntervalFunctions* interval_functions = (IntervalFunctions*)Local::Cast(args.Data())->Value(); args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, false)); } void worker_set_timeout(const FunctionCallbackInfo& args) { HandleScope scope(args.GetIsolate()); IntervalFunctions* interval_functions = (IntervalFunctions*)Local::Cast(args.Data())->Value(); args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, true)); } void worker_clear_timer(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); HandleScope scope(isolate); if (args.Length() < 1 || !args[0]->IsInt32()) { return; } Local external = Local::Cast(args.Data()); IntervalFunctions* intervalFunctions = (IntervalFunctions*)external->Value(); int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked(); for (std::vector::iterator it = intervalFunctions->functions.begin(); it != intervalFunctions->functions.end(); ++it) { if (it->id == id) { it->function.Reset(); intervalFunctions->functions.erase(it); return; } } } void worker_thread_func(void* param) { WorkerData* worker_data = (WorkerData*)param; WorkerMessagePort* message_port = worker_data->messagePort; Isolate::CreateParams create_params; auto array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); create_params.array_buffer_allocator = array_buffer_allocator; Isolate* isolate = Isolate::New(create_params); { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); Local global = ObjectTemplate::New(isolate); Local extern_message_port = External::New(isolate, message_port); global->Set(String::NewFromUtf8(isolate, "postMessage").ToLocalChecked(), FunctionTemplate::New(isolate, worker_post_message, extern_message_port)); global->SetAccessor(String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), (AccessorGetterCallback)worker_onmessage_get, (AccessorSetterCallback)worker_onmessage_set, extern_message_port); global->Set(String::NewFromUtf8(isolate, "addEventListener").ToLocalChecked(), FunctionTemplate::New(isolate, worker_add_event_listener, extern_message_port)); IntervalFunctions interval_functions; interval_functions.latestId = 0; Local extern_interval_functions = External::New(isolate, &interval_functions); global->Set(String::NewFromUtf8(isolate, "setInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_set_interval, extern_interval_functions)); global->Set(String::NewFromUtf8(isolate, "clearInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_clear_timer, extern_interval_functions)); global->Set(String::NewFromUtf8(isolate, "setTimeout").ToLocalChecked(), FunctionTemplate::New(isolate, worker_set_timeout, extern_interval_functions)); global->Set(String::NewFromUtf8(isolate, "clearTimeout").ToLocalChecked(), FunctionTemplate::New(isolate, worker_clear_timer, extern_interval_functions)); Local console_template = ObjectTemplate::New(isolate); console_template->Set(String::NewFromUtf8(isolate, "log").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); console_template->Set(String::NewFromUtf8(isolate, "warn").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); console_template->Set(String::NewFromUtf8(isolate, "error").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); console_template->Set(String::NewFromUtf8(isolate, "info").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); console_template->Set(String::NewFromUtf8(isolate, "debug").ToLocalChecked(), FunctionTemplate::New(isolate, worker_console_log)); global->Set(String::NewFromUtf8(isolate, "console").ToLocalChecked(), console_template); Local context = Context::New(isolate, nullptr, global); Global global_context; global_context.Reset(isolate, context); Context::Scope context_scope(context); Local global_obj = context->Global(); global_obj->Set(context, String::NewFromUtf8(isolate, "self").ToLocalChecked(), global_obj).ToChecked(); global_obj->Set(context, String::NewFromUtf8(isolate, "WorkerGlobalScope").ToLocalChecked(), global_obj).ToChecked(); global_obj->Set(context, String::NewFromUtf8(isolate, "globalThis").ToLocalChecked(), global_obj).ToChecked(); Local navigator = Object::New(isolate); navigator->Set(context, String::NewFromUtf8(isolate, "hardwareConcurrency").ToLocalChecked(), Integer::New(isolate, kinc_hardware_threads())).ToChecked(); navigator->Set(context, String::NewFromUtf8(isolate, "userAgent").ToLocalChecked(), String::NewFromUtf8(isolate, "RunT/1.0").ToLocalChecked()).ToChecked(); navigator->Set(context, String::NewFromUtf8(isolate, "language").ToLocalChecked(), String::NewFromUtf8(isolate, "en-US").ToLocalChecked()).ToChecked(); global_obj->Set(context, String::NewFromUtf8(isolate, "navigator").ToLocalChecked(), navigator).ToChecked(); bind_worker_class(isolate, global_context); kinc_file_reader_t reader; if (!kinc_file_reader_open(&reader, worker_data->fileName, KINC_FILE_TYPE_ASSET)) { kinc_log(KINC_LOG_LEVEL_ERROR, "Could not load file %s for worker thread", worker_data->fileName); exit(1); } size_t file_size = kinc_file_reader_size(&reader); char* code = new char[file_size + 1]; kinc_file_reader_read(&reader, code, file_size); code[file_size] = 0; kinc_file_reader_close(&reader); Local source = String::NewFromUtf8(isolate, code, NewStringType::kNormal).ToLocalChecked(); TryCatch try_catch(isolate); Local