Files
LNXRNT/Sources/worker.cpp

781 lines
28 KiB
C++
Raw Normal View History

2025-01-29 10:55:49 +01:00
#include "worker.h"
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <kinc/log.h>
#include <kinc/threads/thread.h>
#include <kinc/threads/mutex.h>
2026-07-09 23:51:42 -07:00
#include <kinc/threads/event.h>
2025-01-29 10:55:49 +01:00
#include <kinc/io/filereader.h>
#include <kinc/system.h>
2026-07-09 23:51:42 -07:00
#include <v8-value-serializer.h>
#include <v8-wasm.h>
#include <v8-array-buffer.h>
#include <atomic>
#include <memory>
2025-01-29 10:55:49 +01:00
using namespace v8;
namespace {
const uint32_t worker_data_slot = 0;
2026-07-09 23:51:42 -07:00
struct TransferData;
2025-01-29 10:55:49 +01:00
struct WorkerMessage {
2026-07-09 23:51:42 -07:00
uint8_t* data;
size_t size;
TransferData* transferData;
2025-01-29 10:55:49 +01:00
};
struct MessageQueue {
std::vector<WorkerMessage> messages;
kinc_mutex_t messageMutex;
2026-07-09 23:51:42 -07:00
kinc_event_t event;
2025-01-29 10:55:49 +01:00
Global<Function> messageFunc;
2026-07-09 23:51:42 -07:00
const char* label = "?";
2025-01-29 10:55:49 +01:00
};
struct WorkerMessagePort {
MessageQueue workerMessages;
MessageQueue ownerMessages;
2026-07-09 23:51:42 -07:00
std::atomic<bool> isTerminated{false};
2025-01-29 10:55:49 +01:00
};
struct WorkerData {
char fileName[256];
WorkerMessagePort* messagePort;
};
struct OwnedWorker {
kinc_thread_t* workerThread;
WorkerMessagePort* workerMessagePort;
};
struct ContextData {
std::vector<OwnedWorker> workers;
};
struct IntervalFunction {
Global<Function> function;
double interval;
double nextCallTime;
int id;
2026-07-09 23:51:42 -07:00
bool isTimeout;
2025-01-29 10:55:49 +01:00
};
struct IntervalFunctions {
std::vector<IntervalFunction> functions;
int latestId;
};
2026-07-09 23:51:42 -07:00
struct TransferData {
std::vector<std::shared_ptr<BackingStore>> sharedArrayBuffers;
std::vector<CompiledWasmModule> wasmModules;
std::unique_ptr<SharedValueConveyor> sharedValueDispatch;
};
class SerializerDelegate : public ValueSerializer::Delegate {
public:
TransferData* transferData;
SerializerDelegate(TransferData* data) : transferData(data) {}
void ThrowDataCloneError(Local<String> message) override {
Isolate* isolate = Isolate::GetCurrent();
isolate->ThrowException(message);
}
Maybe<uint32_t> GetSharedArrayBufferId(Isolate* isolate, Local<SharedArrayBuffer> 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<uint32_t> GetWasmModuleTransferId(Isolate* isolate, Local<WasmModuleObject> 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<SharedValueConveyor>(std::move(conveyor));
return true;
}
};
class DeserializerDelegate : public ValueDeserializer::Delegate {
public:
TransferData* transferData;
DeserializerDelegate(TransferData* data) : transferData(data) {}
MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(Isolate* isolate, uint32_t clone_id) override {
if (clone_id >= transferData->sharedArrayBuffers.size()) {
return MaybeLocal<SharedArrayBuffer>();
}
return SharedArrayBuffer::New(isolate, transferData->sharedArrayBuffers[clone_id]);
}
MaybeLocal<WasmModuleObject> GetWasmModuleFromId(Isolate* isolate, uint32_t transfer_id) override {
if (transfer_id >= transferData->wasmModules.size()) {
return MaybeLocal<WasmModuleObject>();
}
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);
2025-01-29 10:55:49 +01:00
void set_onmessage(MessageQueue* messageQueue, Isolate* isolate, Local<Value> callback) {
if (callback->IsFunction()) {
messageQueue->messageFunc.Reset(isolate, Local<Function>::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<Value> messageObject) {
2026-07-09 23:51:42 -07:00
Local<Context> context = isolate->GetCurrentContext();
TransferData* transferData = new TransferData();
SerializerDelegate delegate(transferData);
ValueSerializer serializer(isolate, &delegate);
serializer.WriteHeader();
TryCatch try_catch(isolate);
Maybe<bool> 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<uint8_t*, size_t> released = serializer.Release();
2025-01-29 10:55:49 +01:00
WorkerMessage message;
2026-07-09 23:51:42 -07:00
message.data = released.first;
message.size = released.second;
message.transferData = transferData;
2025-01-29 10:55:49 +01:00
kinc_mutex_lock(&messageQueue->messageMutex);
messageQueue->messages.push_back(message);
kinc_mutex_unlock(&messageQueue->messageMutex);
2026-07-09 23:51:42 -07:00
kinc_event_signal(&messageQueue->event);
2025-01-29 10:55:49 +01:00
}
void handle_message_queue(Isolate* isolate, const Global<Context>& context, MessageQueue* messageQueue) {
2026-07-09 23:51:42 -07:00
kinc_mutex_lock(&messageQueue->messageMutex);
if (messageQueue->messages.empty()) {
kinc_mutex_unlock(&messageQueue->messageMutex);
return;
}
std::vector<WorkerMessage> messages;
messages.swap(messageQueue->messages);
kinc_mutex_unlock(&messageQueue->messageMutex);
2025-01-29 10:55:49 +01:00
if (messageQueue->messageFunc.IsEmpty()) {
2026-07-09 23:51:42 -07:00
for (WorkerMessage& message : messages) {
free(message.data);
delete message.transferData;
}
2025-01-29 10:55:49 +01:00
return;
}
HandleScope scope(isolate);
Local<Context> current_context = Local<Context>::New(isolate, context);
Context::Scope context_scope(current_context);
2026-07-09 23:51:42 -07:00
for (WorkerMessage& message : messages) {
DeserializerDelegate delegate(message.transferData);
ValueDeserializer deserializer(isolate, message.data, message.size, &delegate);
Maybe<bool> headerResult = deserializer.ReadHeader(current_context);
if (headerResult.IsNothing()) {
free(message.data);
delete message.transferData;
continue;
}
MaybeLocal<Value> value = deserializer.ReadValue(current_context);
free(message.data);
if (value.IsEmpty()) {
delete message.transferData;
continue;
}
2025-01-29 10:55:49 +01:00
Local<Object> object = Object::New(isolate);
object->Set(current_context, String::NewFromUtf8(isolate, "data").ToLocalChecked(), value.ToLocalChecked());
Local<Value> args[] = { object };
Local<Function> message_func = messageQueue->messageFunc.Get(isolate);
2026-07-09 23:51:42 -07:00
TryCatch try_catch(isolate);
Local<Value> callResult;
if (!message_func->Call(current_context, current_context->Global(), 1, args).ToLocal(&callResult)) {
handle_exception(isolate, &try_catch);
}
delete message.transferData;
2025-01-29 10:55:49 +01:00
}
2026-07-09 23:51:42 -07:00
}
2025-01-29 10:55:49 +01:00
2026-07-09 23:51:42 -07:00
void worker_console_log(const FunctionCallbackInfo<Value>& 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());
2025-01-29 10:55:49 +01:00
}
void handle_exception(Isolate* isolate, TryCatch* try_catch) {
MaybeLocal<Value> trace = try_catch->StackTrace(isolate->GetCurrentContext());
if (trace.IsEmpty()) {
2026-07-09 23:51:42 -07:00
MaybeLocal<v8::Message> 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);
}
2025-01-29 10:55:49 +01:00
}
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<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1) {
return;
}
Local<External> external = Local<External>::Cast(args.Data());
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
post_message(&messagePort->workerMessages, isolate, args[0]);
}
void worker_onmessage_get(Local<String> property, const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
HandleScope scope(isolate);
Local<External> external = Local<External>::Cast(info.Data());
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
info.GetReturnValue().Set(messagePort->ownerMessages.messageFunc);
}
void worker_onmessage_set(Local<String> property, Local<Value> value, const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
HandleScope scope(isolate);
Local<External> external = Local<External>::Cast(info.Data());
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
set_onmessage(&messagePort->ownerMessages, isolate, value);
}
void worker_add_event_listener(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2) {
return;
}
Local<String> name = Local<String>::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> external = Local<External>::Cast(args.Data());
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
set_onmessage(&messagePort->ownerMessages, isolate, args[1]);
}
2026-07-09 23:51:42 -07:00
int add_timer(IntervalFunctions* interval_functions, Isolate* isolate, const FunctionCallbackInfo<Value>& 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;
}
2025-01-29 10:55:49 +01:00
Local<Function> function = Local<Function>::Cast(args[0]);
2026-07-09 23:51:42 -07:00
double delay;
2025-01-29 10:55:49 +01:00
if (args.Length() > 1) {
2026-07-09 23:51:42 -07:00
if (!args[1]->IsNumber()) {
isolate->ThrowException(String::NewFromUtf8(isolate, "Timer delay must be a number").ToLocalChecked());
return -1;
}
delay = Local<Number>::Cast(args[1])->Value() / 1000.0;
2025-01-29 10:55:49 +01:00
}
else {
2026-07-09 23:51:42 -07:00
delay = is_timeout ? 0.0 : 0.016;
2025-01-29 10:55:49 +01:00
}
interval_functions->functions.emplace_back();
2026-07-09 23:51:42 -07:00
IntervalFunction& interval_function = interval_functions->functions.back();
2025-01-29 10:55:49 +01:00
interval_function.function.Reset(isolate, function);
2026-07-09 23:51:42 -07:00
interval_function.interval = delay;
interval_function.nextCallTime = kinc_time() + delay;
2025-01-29 10:55:49 +01:00
interval_function.id = interval_functions->latestId;
2026-07-09 23:51:42 -07:00
interval_function.isTimeout = is_timeout;
2025-01-29 10:55:49 +01:00
interval_functions->latestId++;
2026-07-09 23:51:42 -07:00
return interval_function.id;
}
2025-01-29 10:55:49 +01:00
2026-07-09 23:51:42 -07:00
void worker_set_interval(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
IntervalFunctions* interval_functions = (IntervalFunctions*)Local<External>::Cast(args.Data())->Value();
args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, false));
}
void worker_set_timeout(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(args.GetIsolate());
IntervalFunctions* interval_functions = (IntervalFunctions*)Local<External>::Cast(args.Data())->Value();
args.GetReturnValue().Set(add_timer(interval_functions, args.GetIsolate(), args, true));
2025-01-29 10:55:49 +01:00
}
2026-07-09 23:51:42 -07:00
void worker_clear_timer(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1 || !args[0]->IsInt32()) {
return;
}
2025-01-29 10:55:49 +01:00
Local<External> external = Local<External>::Cast(args.Data());
IntervalFunctions* intervalFunctions = (IntervalFunctions*)external->Value();
2026-07-09 23:51:42 -07:00
int id = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
2025-01-29 10:55:49 +01:00
for (std::vector<IntervalFunction>::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;
2026-07-09 23:51:42 -07:00
auto array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
create_params.array_buffer_allocator = array_buffer_allocator;
2025-01-29 10:55:49 +01:00
Isolate* isolate = Isolate::New(create_params);
2026-07-09 23:51:42 -07:00
{
2025-01-29 10:55:49 +01:00
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
Local<External> 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<External> extern_interval_functions = External::New(isolate, &interval_functions);
global->Set(String::NewFromUtf8(isolate, "setInterval").ToLocalChecked(), FunctionTemplate::New(isolate, worker_set_interval, extern_interval_functions));
2026-07-09 23:51:42 -07:00
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<ObjectTemplate> 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);
2025-01-29 10:55:49 +01:00
Local<Context> context = Context::New(isolate, nullptr, global);
Global<Context> global_context;
global_context.Reset(isolate, context);
Context::Scope context_scope(context);
2026-07-09 23:51:42 -07:00
Local<Object> 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<Object> 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();
2025-01-29 10:55:49 +01:00
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<String> source = String::NewFromUtf8(isolate, code, NewStringType::kNormal).ToLocalChecked();
TryCatch try_catch(isolate);
2026-07-09 23:51:42 -07:00
Local<Script> compiled_script;
if (!Script::Compile(isolate->GetCurrentContext(), source).ToLocal(&compiled_script)) {
2025-01-29 10:55:49 +01:00
handle_exception(isolate, &try_catch);
}
2026-07-09 23:51:42 -07:00
else {
Local<Value> result;
if (!compiled_script->Run(context).ToLocal(&result)) {
handle_exception(isolate, &try_catch);
}
MicrotasksScope::PerformCheckpoint(isolate);
}
2025-01-29 10:55:49 +01:00
2026-07-09 23:51:42 -07:00
std::vector<int> due_ids;
due_ids.reserve(8);
while (!message_port->isTerminated.load()) {
HandleScope loop_scope(isolate);
2025-01-29 10:55:49 +01:00
double time = kinc_time();
2026-07-09 23:51:42 -07:00
bool had_work = false;
due_ids.clear();
for (const IntervalFunction& fn : interval_functions.functions) {
if (fn.nextCallTime <= time) {
due_ids.push_back(fn.id);
}
}
for (int due_id : due_ids) {
int idx = -1;
for (int j = 0; j < (int)interval_functions.functions.size(); ++j) {
if (interval_functions.functions[j].id == due_id) {
idx = j;
break;
2025-01-29 10:55:49 +01:00
}
2026-07-09 23:51:42 -07:00
}
if (idx < 0) {
continue;
}
had_work = true;
IntervalFunction& fn = interval_functions.functions[idx];
Local<v8::Function> func = Local<v8::Function>::New(isolate, fn.function);
if (fn.isTimeout) {
fn.function.Reset();
interval_functions.functions.erase(interval_functions.functions.begin() + idx);
}
else {
fn.nextCallTime = time + fn.interval;
}
2025-01-29 10:55:49 +01:00
2026-07-09 23:51:42 -07:00
TryCatch try_catch(isolate);
Local<Value> result;
if (!func->Call(context, context->Global(), 0, NULL).ToLocal(&result)) {
handle_exception(isolate, &try_catch);
2025-01-29 10:55:49 +01:00
}
}
handle_message_queue(isolate, global_context, &message_port->ownerMessages);
2026-07-09 23:51:42 -07:00
MicrotasksScope::PerformCheckpoint(isolate);
if (!had_work && !message_port->isTerminated.load()) {
double timeout = 1.0;
for (const IntervalFunction& fn : interval_functions.functions) {
double dt = fn.nextCallTime - time;
if (dt < timeout) {
timeout = dt;
}
}
if (timeout < 0.0) {
timeout = 0.0;
}
kinc_event_try_to_wait(&message_port->ownerMessages.event, timeout);
}
2025-01-29 10:55:49 +01:00
}
2026-07-09 23:51:42 -07:00
2025-01-29 10:55:49 +01:00
ContextData* context_data = (ContextData*)isolate->GetData(worker_data_slot);
for (int i = 0; i < context_data->workers.size(); ++i) {
WorkerMessagePort* workerPort = context_data->workers[i].workerMessagePort;
2026-07-09 23:51:42 -07:00
workerPort->isTerminated.store(true);
kinc_event_signal(&workerPort->ownerMessages.event);
2025-01-29 10:55:49 +01:00
kinc_thread_wait_and_destroy(context_data->workers[i].workerThread);
delete context_data->workers[i].workerThread;
2026-07-09 23:51:42 -07:00
for (auto& msg : workerPort->ownerMessages.messages) {
free(msg.data);
delete msg.transferData;
}
for (auto& msg : workerPort->workerMessages.messages) {
free(msg.data);
delete msg.transferData;
}
2025-01-29 10:55:49 +01:00
kinc_mutex_destroy(&workerPort->ownerMessages.messageMutex);
kinc_mutex_destroy(&workerPort->workerMessages.messageMutex);
2026-07-09 23:51:42 -07:00
kinc_event_destroy(&workerPort->ownerMessages.event);
kinc_event_destroy(&workerPort->workerMessages.event);
2025-01-29 10:55:49 +01:00
delete workerPort;
}
delete[] code;
delete worker_data;
delete context_data;
isolate->TerminateExecution();
2026-07-09 23:51:42 -07:00
}
isolate->Dispose();
delete array_buffer_allocator;
2025-01-29 10:55:49 +01:00
}
void owner_onmessage_get(Local<String> property,const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
HandleScope scope(isolate);
Local<External> external = Local<External>::Cast(info.This()->GetInternalField(0));
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
info.GetReturnValue().Set(messagePort->workerMessages.messageFunc);
}
void owner_onmessage_set(Local<String> property, Local<Value> value, const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
HandleScope scope(isolate);
Local<External> external = Local<External>::Cast(info.This()->GetInternalField(0));
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
set_onmessage(&messagePort->workerMessages, isolate, value);
}
void owner_add_event_listener(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 2) {
return;
}
Local<String> name = Local<String>::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> external = Local<External>::Cast(args.This()->GetInternalField(0));
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
set_onmessage(&messagePort->workerMessages, isolate, args[1]);
}
void owner_post_message(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (args.Length() < 1) {
return;
}
Local<External> external = Local<External>::Cast(args.This()->GetInternalField(0));
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
post_message(&messagePort->ownerMessages, isolate, args[0]);
}
void owner_worker_terminate(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
Local<External> external = Local<External>::Cast(args.This()->GetInternalField(0));
WorkerMessagePort* messagePort = (WorkerMessagePort*)external->Value();
2026-07-09 23:51:42 -07:00
messagePort->isTerminated.store(true);
kinc_event_signal(&messagePort->ownerMessages.event);
2025-01-29 10:55:49 +01:00
ContextData* context_data = (ContextData*)isolate->GetData(worker_data_slot);
for (std::vector<OwnedWorker>::iterator it = context_data->workers.begin(); it != context_data->workers.end(); ++it) {
if (it->workerMessagePort == messagePort) {
kinc_thread_wait_and_destroy(it->workerThread);
context_data->workers.erase(it);
break;
}
}
2026-07-09 23:51:42 -07:00
for (auto& msg : messagePort->ownerMessages.messages) {
free(msg.data);
delete msg.transferData;
}
for (auto& msg : messagePort->workerMessages.messages) {
free(msg.data);
delete msg.transferData;
}
2025-01-29 10:55:49 +01:00
kinc_mutex_destroy(&messagePort->ownerMessages.messageMutex);
kinc_mutex_destroy(&messagePort->workerMessages.messageMutex);
2026-07-09 23:51:42 -07:00
kinc_event_destroy(&messagePort->ownerMessages.event);
kinc_event_destroy(&messagePort->workerMessages.event);
2025-01-29 10:55:49 +01:00
delete messagePort;
args.This()->SetInternalField(0, External::New(isolate, nullptr));
}
void worker_constructor(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
if (!args.IsConstructCall()) {
isolate->ThrowException(String::NewFromUtf8(isolate, "Worker constructor: 'new' is required").ToLocalChecked());
return;
}
if (args.Length() < 1) {
isolate->ThrowException(String::NewFromUtf8(isolate, "Worker constructor: At least 1 argument required, but only 0 passed").ToLocalChecked());
return;
}
WorkerMessagePort* messagePort = new WorkerMessagePort;
2026-07-09 23:51:42 -07:00
messagePort->ownerMessages.label = "ownerMessages(main->worker)";
messagePort->workerMessages.label = "workerMessages(worker->main)";
2025-01-29 10:55:49 +01:00
kinc_mutex_init(&messagePort->ownerMessages.messageMutex);
kinc_mutex_init(&messagePort->workerMessages.messageMutex);
2026-07-09 23:51:42 -07:00
kinc_event_init(&messagePort->ownerMessages.event, true);
kinc_event_init(&messagePort->workerMessages.event, true);
2025-01-29 10:55:49 +01:00
Local<Context> context = isolate->GetCurrentContext();
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->SetInternalFieldCount(1);
Local<Object> worker = templ->NewInstance(context).ToLocalChecked();
worker->SetInternalField(0, External::New(isolate, messagePort));
Local<Value> prototype = context->Global()->Get(context, String::NewFromUtf8(isolate, "WorkerPrototype").ToLocalChecked()).ToLocalChecked();
worker->SetPrototype(context, prototype);
args.GetReturnValue().Set(worker);
// Create thread and add it to list in context data
OwnedWorker owned_worker;
owned_worker.workerMessagePort = messagePort;
owned_worker.workerThread = new kinc_thread_t;
WorkerData* worker_data = new WorkerData;
worker_data->messagePort = messagePort;
Local<String> file_name = Local<String>::Cast(args[0]);
int length = file_name->WriteUtf8(isolate, worker_data->fileName, 255);
worker_data->fileName[length] = 0;
// Check if file exists before we create thread
2026-07-09 23:51:42 -07:00
kinc_file_reader_t fileCheck;
2025-01-29 10:55:49 +01:00
if (!kinc_file_reader_open(&fileCheck, worker_data->fileName, KINC_FILE_TYPE_ASSET)) {
const char* errorFmt = "Worker constructor: file %s does not exist";
int length = snprintf(nullptr, 0, errorFmt, worker_data->fileName);
char* errorMessage = (char*)malloc(length + 1);
2026-07-09 23:51:42 -07:00
snprintf(errorMessage, length + 1, errorFmt, worker_data->fileName);
2025-01-29 10:55:49 +01:00
isolate->ThrowException(String::NewFromUtf8(isolate, errorMessage).ToLocalChecked());
free(errorMessage);
2026-07-09 23:51:42 -07:00
delete owned_worker.workerThread;
delete worker_data;
kinc_mutex_destroy(&messagePort->ownerMessages.messageMutex);
kinc_mutex_destroy(&messagePort->workerMessages.messageMutex);
kinc_event_destroy(&messagePort->ownerMessages.event);
kinc_event_destroy(&messagePort->workerMessages.event);
delete messagePort;
args.This()->SetInternalField(0, External::New(isolate, nullptr));
2025-01-29 10:55:49 +01:00
return;
}
kinc_file_reader_close(&fileCheck);
kinc_thread_init(owned_worker.workerThread, worker_thread_func, worker_data);
ContextData* context_data = (ContextData*)(isolate->GetData(worker_data_slot));
context_data->workers.push_back(owned_worker);
}
}
void bind_worker_class(Isolate* isolate, const v8::Global<v8::Context>& context) {
2026-02-20 23:40:15 -08:00
// TODO: verify lock
Locker locker{isolate};
2025-01-29 10:55:49 +01:00
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> current_context = Local<Context>::New(isolate, context);
Context::Scope context_scope(current_context);
Local<Object> global = current_context->Global();
global->Set(current_context, String::NewFromUtf8(isolate, "Worker").ToLocalChecked(), Function::New(current_context, worker_constructor).ToLocalChecked());
Local<ObjectTemplate> worker_prototype_templ = ObjectTemplate::New(isolate);
worker_prototype_templ->SetAccessor(String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), (AccessorGetterCallback)owner_onmessage_get, (AccessorSetterCallback)owner_onmessage_set);
worker_prototype_templ->Set(String::NewFromUtf8(isolate, "addEventListener").ToLocalChecked(), FunctionTemplate::New(isolate, owner_add_event_listener));
worker_prototype_templ->Set(String::NewFromUtf8(isolate, "postMessage").ToLocalChecked(), FunctionTemplate::New(isolate, owner_post_message));
worker_prototype_templ->Set(String::NewFromUtf8(isolate, "terminate").ToLocalChecked(), FunctionTemplate::New(isolate, owner_worker_terminate));
global->Set(current_context, String::NewFromUtf8(isolate, "WorkerPrototype").ToLocalChecked(), worker_prototype_templ->NewInstance(current_context).ToLocalChecked());
ContextData* context_data = new ContextData;
isolate->SetData(worker_data_slot, context_data);
}
void handle_worker_messages(v8::Isolate* isolate, const v8::Global<v8::Context>& context) {
2026-02-20 23:40:15 -08:00
// TODO: re-investigate
if (!isolate) {
return;
}
2025-01-29 10:55:49 +01:00
Locker locker(isolate);
2026-02-20 23:40:15 -08:00
Isolate::Scope isolate_scope(isolate);
2025-01-29 10:55:49 +01:00
HandleScope handle_scope(isolate);
ContextData* context_data = (ContextData*)(isolate->GetData(worker_data_slot));
2026-02-20 23:40:15 -08:00
if (!context_data) {
return;
}
2025-01-29 10:55:49 +01:00
if (context_data->workers.size() == 0) {
return;
}
for (OwnedWorker& worker : context_data->workers) {
handle_message_queue(isolate, context, &worker.workerMessagePort->workerMessages);
}
}