From 2c87f3593daf4df223e79979d9949dfeb0fd74c6 Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Thu, 5 Dec 2019 04:07:18 +0100 Subject: [PATCH] Initial Worker threads/context aware support --- build.c | 8 ++-- examples/WorkerThreads.js | 33 +++++++++++++++ src/AppWrapper.h | 86 +++++++++++++++++++++++---------------- src/HttpRequestWrapper.h | 22 ++++++---- src/HttpResponseWrapper.h | 34 +++++++++------- src/Utilities.h | 19 ++++++++- src/WebSocketWrapper.h | 26 ++++++------ src/addon.cpp | 81 ++++++++++++++++++++---------------- uWebSockets | 2 +- 9 files changed, 199 insertions(+), 112 deletions(-) create mode 100644 examples/WorkerThreads.js diff --git a/build.c b/build.c index 4121562..faaeeab 100644 --- a/build.c +++ b/build.c @@ -55,8 +55,8 @@ void prepare() { /* Build for Unix systems */ void build(char *compiler, char *cpp_compiler, char *cpp_linker, char *os, char *arch) { - char *c_shared = "-DLIBUS_USE_LIBUV -flto -O3 -c -fPIC -I uWebSockets/uSockets/src uWebSockets/uSockets/src/*.c uWebSockets/uSockets/src/eventing/*.c"; - char *cpp_shared = "-DLIBUS_USE_LIBUV -flto -O3 -c -fPIC -std=c++17 -I uWebSockets/uSockets/src -I uWebSockets/src src/addon.cpp"; + char *c_shared = "-DLIBUS_USE_LIBUV -DLIBUS_USE_OPENSSL -flto -O3 -c -fPIC -I uWebSockets/uSockets/src uWebSockets/uSockets/src/*.c uWebSockets/uSockets/src/eventing/*.c uWebSockets/uSockets/src/crypto/*.c"; + char *cpp_shared = "-DLIBUS_USE_LIBUV -DLIBUS_USE_OPENSSL -flto -O3 -c -fPIC -std=c++17 -I uWebSockets/uSockets/src -I uWebSockets/src src/addon.cpp"; for (unsigned int i = 0; i < sizeof(versions) / sizeof(struct node_version); i++) { run("%s %s -I targets/node-%s/include/node", compiler, c_shared, versions[i].name); @@ -77,8 +77,8 @@ void copy_files() { void build_windows(char *arch) { /* For all versions */ for (unsigned int i = 0; i < sizeof(versions) / sizeof(struct node_version); i++) { - run("cl /D \"LIBUS_USE_LIBUV\" /std:c++17 /I uWebSockets/uSockets/src uWebSockets/uSockets/src/*.c " - "uWebSockets/uSockets/src/eventing/*.c /I targets/node-%s/include/node /I uWebSockets/src /EHsc " + run("cl /D \"LIBUS_USE_LIBUV\" /D \"LIBUS_USE_OPENSSL\" /std:c++17 /I uWebSockets/uSockets/src uWebSockets/uSockets/src/*.c " + "uWebSockets/uSockets/src/eventing/*.c uWebSockets/uSockets/src/crypto/*.c /I targets/node-%s/include/node /I uWebSockets/src /EHsc " "/Ox /LD /Fedist/uws_win32_%s_%s.node src/addon.cpp targets/node-%s/node.lib", versions[i].name, arch, versions[i].abi, versions[i].name); } diff --git a/examples/WorkerThreads.js b/examples/WorkerThreads.js new file mode 100644 index 0000000..043875c --- /dev/null +++ b/examples/WorkerThreads.js @@ -0,0 +1,33 @@ +/* This example spawns two worker threads, each with their own + * server listening to the same port (Linux feature). */ + +const uWS = require('../dist/uws.js'); +const port = 9001; +const { Worker, isMainThread, threadId } = require('worker_threads'); +const os = require('os'); + +if (isMainThread) { + /* Main thread loops over all CPUs */ + /* In this case we only spawn two (hardcoded) */ + /*os.cpus()*/[0, 1].forEach(() => { + /* Spawn a new thread running this source file */ + new Worker(__filename); + }); + + /* I guess main thread joins by default? */ +} else { + /* Here we are inside a worker thread */ + const app = uWS./*SSL*/App({ + key_file_name: 'misc/key.pem', + cert_file_name: 'misc/cert.pem', + passphrase: '1234' + }).get('/*', (res, req) => { + res.end('Hello Worker!'); + }).listen(port, (token) => { + if (token) { + console.log('Listening to port ' + port + ' from thread ' + threadId); + } else { + console.log('Failed to listen to port ' + port + ' from thread ' + threadId); + } + }); +} diff --git a/src/AppWrapper.h b/src/AppWrapper.h index 93cba34..67441b5 100644 --- a/src/AppWrapper.h +++ b/src/AppWrapper.h @@ -6,6 +6,11 @@ using namespace v8; /* uWS.App.ws('/pattern', behavior) */ template void uWS_App_ws(const FunctionCallbackInfo &args) { + + Isolate *isolate = args.GetIsolate(); + + PerContextData *perContextData = (PerContextData *) Local::Cast(args.Data())->Value(); + APP *app = (APP *) args.Holder()->GetAlignedPointerFromInternalField(0); /* This one is default constructed with defaults */ typename APP::WebSocketBehavior behavior = {}; @@ -63,15 +68,16 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { } /* Open handler is NOT optional for the wrapper */ - behavior.open = [openPf = std::move(openPf)](auto *ws, auto *req) { + behavior.open = [openPf = std::move(openPf), perContextData](auto *ws, auto *req) { + Isolate *isolate = perContextData->isolate; HandleScope hs(isolate); /* Create a new websocket object */ - Local wsObject = WebSocketWrapper::getWsInstance(); + Local wsObject = perContextData->wsTemplate[getAppTypeIndex()].Get(isolate)->Clone(); wsObject->SetAlignedPointerInInternalField(0, ws); /* Create the HttpRequest wrapper */ - Local reqObject = HttpRequestWrapper::getReqInstance(); + Local reqObject = perContextData->reqTemplate.Get(isolate)->Clone(); reqObject->SetAlignedPointerInInternalField(0, req); /* Attach a new V8 object with pointer to us, to us */ @@ -88,7 +94,7 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { /* Message handler is always optional */ if (messagePf != Undefined(isolate)) { - behavior.message = [messagePf = std::move(messagePf)](auto *ws, std::string_view message, uWS::OpCode opCode) { + behavior.message = [messagePf = std::move(messagePf), isolate](auto *ws, std::string_view message, uWS::OpCode opCode) { HandleScope hs(isolate); Local messageArrayBuffer = ArrayBuffer::New(isolate, (void *) message.data(), message.length()); @@ -106,7 +112,7 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { /* Drain handler is always optional */ if (drainPf != Undefined(isolate)) { - behavior.drain = [drainPf = std::move(drainPf)](auto *ws) { + behavior.drain = [drainPf = std::move(drainPf), isolate](auto *ws) { HandleScope hs(isolate); PerSocketData *perSocketData = (PerSocketData *) ws->getUserData(); @@ -126,7 +132,7 @@ void uWS_App_ws(const FunctionCallbackInfo &args) { }; /* Close handler is NOT optional for the wrapper */ - behavior.close = [closePf = std::move(closePf)](auto *ws, int code, std::string_view message) { + behavior.close = [closePf = std::move(closePf), isolate](auto *ws, int code, std::string_view message) { HandleScope hs(isolate); Local messageArrayBuffer = ArrayBuffer::New(isolate, (void *) message.data(), message.length()); @@ -165,21 +171,22 @@ void uWS_App_get(F f, const FunctionCallbackInfo &args) { return; } - /* todo: make it UniquePersistent */ - std::shared_ptr> pf(new Persistent); - pf->Reset(args.GetIsolate(), Local::Cast(args[1])); + /* This function requires perContextData */ + PerContextData *perContextData = (PerContextData *) Local::Cast(args.Data())->Value(); + UniquePersistent cb(args.GetIsolate(), Local::Cast(args[1])); - (app->*f)(std::string(pattern.getString()), [pf](auto *res, auto *req) { + (app->*f)(std::string(pattern.getString()), [cb = std::move(cb), perContextData](auto *res, auto *req) { + Isolate *isolate = perContextData->isolate; HandleScope hs(isolate); - Local resObject = HttpResponseWrapper::getResInstance(); + Local resObject = perContextData->resTemplate[getAppTypeIndex()].Get(isolate)->Clone(); resObject->SetAlignedPointerInInternalField(0, res); - Local reqObject = HttpRequestWrapper::getReqInstance(); + Local reqObject = perContextData->reqTemplate.Get(isolate)->Clone(); reqObject->SetAlignedPointerInInternalField(0, req); Local argv[] = {resObject, reqObject}; - Local::New(isolate, *pf)->Call(isolate->GetCurrentContext(), isolate->GetCurrentContext()->Global(), 2, argv).IsEmpty(); + cb.Get(isolate)->Call(isolate->GetCurrentContext(), isolate->GetCurrentContext()->Global(), 2, argv).IsEmpty(); /* Properly invalidate req */ reqObject->SetAlignedPointerInInternalField(0, nullptr); @@ -195,6 +202,8 @@ template void uWS_App_listen(const FunctionCallbackInfo &args) { APP *app = (APP *) args.Holder()->GetAlignedPointerFromInternalField(0); + Isolate *isolate = args.GetIsolate(); + /* Require at least two arguments */ if (args.Length() < 2) { /* Throw here */ @@ -203,7 +212,7 @@ void uWS_App_listen(const FunctionCallbackInfo &args) { } /* Callback is last */ - auto cb = [&args](auto *token) { + auto cb = [&args, isolate](auto *token) { /* Return a false boolean if listen failed */ Local argv[] = {token ? Local::Cast(External::New(isolate, token)) : Local::Cast(Boolean::New(isolate, false))}; Local::Cast(args[args.Length() - 1])->Call(isolate->GetCurrentContext(), isolate->GetCurrentContext()->Global(), 1, argv).IsEmpty(); @@ -236,6 +245,8 @@ template void uWS_App_publish(const FunctionCallbackInfo &args) { APP *app = (APP *) args.Holder()->GetAlignedPointerFromInternalField(0); + Isolate *isolate = args.GetIsolate(); + NativeString topic(isolate, args[0]); NativeString message(isolate, args[1]); app->publish(topic.getString(), message.getString(), BooleanValue(isolate, args[2]) ? uWS::OpCode::BINARY : uWS::OpCode::TEXT, BooleanValue(isolate, args[3])); @@ -243,10 +254,19 @@ void uWS_App_publish(const FunctionCallbackInfo &args) { template void uWS_App(const FunctionCallbackInfo &args) { + + Isolate *isolate = args.GetIsolate(); + Local appTemplate = FunctionTemplate::New(isolate); APP *app; + /* These won't outlive the function, uSockets will have to copy strings it wants to keep! */ + std::string keyFileName; + std::string certFileName; + std::string passphrase; + std::string dhParamsFileName; + /* Name differs based on type */ if (std::is_same::value) { appTemplate->SetClassName(String::NewFromUtf8(isolate, "uWS.SSLApp", NewStringType::kNormal).ToLocalChecked()); @@ -254,11 +274,6 @@ void uWS_App(const FunctionCallbackInfo &args) { /* We fill these below */ us_socket_context_options_t ssl_options = {}; - static std::string keyFileName; - static std::string certFileName; - static std::string passphrase; - static std::string dhParamsFileName; - /* Read the options object (SSL options) */ if (args.Length() == 1) { @@ -308,6 +323,7 @@ void uWS_App(const FunctionCallbackInfo &args) { ssl_options.ssl_prefer_low_memory_usage = BooleanValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ssl_prefer_low_memory_usage", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked()); } + /* uSockets should really copy strings it wants to keep */ app = new APP(ssl_options); } else { appTemplate->SetClassName(String::NewFromUtf8(isolate, "uWS.App", NewStringType::kNormal).ToLocalChecked()); @@ -326,58 +342,60 @@ void uWS_App(const FunctionCallbackInfo &args) { /* All the http methods */ appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "get", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::get, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "post", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::post, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "options", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::options, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "del", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::del, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "patch", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::patch, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "put", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::put, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "head", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::head, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "connect", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::connect, args); - })); + }, args.Data())); appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "trace", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::trace, args); - })); + }, args.Data())); /* Any http method */ appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "any", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, [](auto &args) { uWS_App_get(&APP::any, args); - })); + }, args.Data())); /* ws, listen */ - appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "ws", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_ws)); - appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "listen", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_listen)); - appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "publish", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_publish)); + appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "ws", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_ws, args.Data())); + appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "listen", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_listen, args.Data())); + appTemplate->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "publish", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App_publish, args.Data())); Local localApp = appTemplate->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); localApp->SetAlignedPointerInInternalField(0, app); + PerContextData *perContextData = (PerContextData *) Local::Cast(args.Data())->Value(); + /* Add this to our delete list */ if constexpr (std::is_same::value) { - sslApps.emplace_back(app); + perContextData->sslApps.emplace_back(app); } else { - apps.emplace_back(app); + perContextData->apps.emplace_back(app); } args.GetReturnValue().Set(localApp); diff --git a/src/HttpRequestWrapper.h b/src/HttpRequestWrapper.h index eb2d4b2..438e566 100644 --- a/src/HttpRequestWrapper.h +++ b/src/HttpRequestWrapper.h @@ -1,13 +1,15 @@ #include "App.h" -#include #include "Utilities.h" + +#include using namespace v8; /* This one is the same for SSL and non-SSL */ struct HttpRequestWrapper { - static Persistent reqTemplate; + /* Unwraps the HttpRequest from V8 object */ static inline uWS::HttpRequest *getHttpRequest(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); /* Thow on deleted request */ auto *req = (uWS::HttpRequest *) args.Holder()->GetAlignedPointerFromInternalField(0); if (!req) { @@ -18,6 +20,7 @@ struct HttpRequestWrapper { /* Takes function of string, string. Returns this (doesn't really but should) */ static void req_forEach(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { Local cb = Local::Cast(args[0]); @@ -32,6 +35,7 @@ struct HttpRequestWrapper { /* Takes int, returns string (must be in bounds) */ static void req_getParameter(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { int index = args[0]->Uint32Value(isolate->GetCurrentContext()).ToChecked(); @@ -43,6 +47,7 @@ struct HttpRequestWrapper { /* Takes nothing, returns string */ static void req_getUrl(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { std::string_view url = req->getUrl(); @@ -53,6 +58,7 @@ struct HttpRequestWrapper { /* Takes String, returns String */ static void req_getHeader(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { NativeString data(args.GetIsolate(), args[0]); @@ -68,6 +74,7 @@ struct HttpRequestWrapper { /* Takes nothing, returns string */ static void req_getMethod(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { std::string_view method = req->getMethod(); @@ -77,6 +84,7 @@ struct HttpRequestWrapper { } static void req_getQuery(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *req = getHttpRequest(args); if (req) { std::string_view query = req->getQuery(); @@ -85,7 +93,8 @@ struct HttpRequestWrapper { } } - static void initReqTemplate() { + /* Returns a clonable object wrapping an HttpRequest */ + static Local init(Isolate *isolate) { /* We do clone every request object, we could share them, they are illegal to use outside the function anyways */ Local reqTemplateLocal = FunctionTemplate::New(isolate); reqTemplateLocal->SetClassName(String::NewFromUtf8(isolate, "uWS.HttpRequest", NewStringType::kNormal).ToLocalChecked()); @@ -101,12 +110,7 @@ struct HttpRequestWrapper { /* Create the template */ Local reqObjectLocal = reqTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); - reqTemplate.Reset(isolate, reqObjectLocal); - } - static Local getReqInstance() { - return Local::New(isolate, reqTemplate)->Clone(); + return reqObjectLocal; } }; - -Persistent HttpRequestWrapper::reqTemplate; diff --git a/src/HttpResponseWrapper.h b/src/HttpResponseWrapper.h index 38c1143..ef2e7c9 100644 --- a/src/HttpResponseWrapper.h +++ b/src/HttpResponseWrapper.h @@ -1,13 +1,14 @@ #include "App.h" -#include #include "Utilities.h" + +#include using namespace v8; struct HttpResponseWrapper { - static Persistent resTemplate[2]; template static inline uWS::HttpResponse *getHttpResponse(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = (uWS::HttpResponse *) args.Holder()->GetAlignedPointerFromInternalField(0); if (!res) { args.GetReturnValue().Set(isolate->ThrowException(String::NewFromUtf8(isolate, "Invalid access of discarded (invalid, deleted) uWS.HttpResponse/SSLHttpResponse.", NewStringType::kNormal).ToLocalChecked())); @@ -34,12 +35,13 @@ struct HttpResponseWrapper { /* Takes function of data and isLast. Expects nothing from callback, returns this */ template static void res_onData(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { /* This thing perfectly fits in with unique_function, and will Reset on destructor */ UniquePersistent p(isolate, Local::Cast(args[0])); - res->onData([p = std::move(p)](std::string_view data, bool last) { + res->onData([p = std::move(p), isolate](std::string_view data, bool last) { HandleScope hs(isolate); Local dataArrayBuffer = ArrayBuffer::New(isolate, (void *) data.data(), data.length()); @@ -57,6 +59,7 @@ struct HttpResponseWrapper { /* Takes nothing, returns nothing. Cb wants nothing returned. */ template static void res_onAborted(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { /* This thing perfectly fits in with unique_function, and will Reset on destructor */ @@ -65,7 +68,7 @@ struct HttpResponseWrapper { /* This is how we capture res (C++ this in invocation of this function) */ UniquePersistent resObject(isolate, args.Holder()); - res->onAborted([p = std::move(p), resObject = std::move(resObject)]() { + res->onAborted([p = std::move(p), resObject = std::move(resObject), isolate]() { HandleScope hs(isolate); /* Mark this resObject invalid */ @@ -81,6 +84,7 @@ struct HttpResponseWrapper { /* Takes nothing, returns arraybuffer */ template static void res_getRemoteAddress(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { std::string_view ip = res->getRemoteAddress(); @@ -93,6 +97,7 @@ struct HttpResponseWrapper { /* Returns the current write offset */ template static void res_getWriteOffset(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { args.GetReturnValue().Set(Integer::New(isolate, getHttpResponse(args)->getWriteOffset())); @@ -102,12 +107,13 @@ struct HttpResponseWrapper { /* Takes function of bool(int), returns this */ template static void res_onWritable(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { /* This thing perfectly fits in with unique_function, and will Reset on destructor */ UniquePersistent p(isolate, Local::Cast(args[0])); - res->onWritable([p = std::move(p)](int offset) -> bool { + res->onWritable([p = std::move(p), isolate](int offset) -> bool { HandleScope hs(isolate); Local argv[] = {Integer::NewFromUnsigned(isolate, offset)}; @@ -161,6 +167,7 @@ struct HttpResponseWrapper { /* Takes data and optionally totalLength, returns true for success, false for backpressure */ template static void res_tryEnd(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { NativeString data(args.GetIsolate(), args[0]); @@ -192,6 +199,7 @@ struct HttpResponseWrapper { /* Takes data, returns true for success, false for backpressure */ template static void res_write(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { NativeString data(args.GetIsolate(), args[0]); @@ -207,6 +215,7 @@ struct HttpResponseWrapper { /* Takes key, value. Returns this */ template static void res_writeHeader(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { NativeString header(args.GetIsolate(), args[0]); @@ -226,10 +235,11 @@ struct HttpResponseWrapper { /* Takes function, returns this (EXPERIMENTAL) */ template static void res_cork(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *res = getHttpResponse(args); if (res) { - res->cork([cb = Local::Cast(args[0])]() { + res->cork([cb = Local::Cast(args[0]), isolate]() { cb->Call(isolate->GetCurrentContext(), isolate->GetCurrentContext()->Global(), 0, nullptr).IsEmpty(); }); @@ -238,7 +248,7 @@ struct HttpResponseWrapper { } template - static void initResTemplate() { + static Local init(Isolate *isolate) { Local resTemplateLocal = FunctionTemplate::New(isolate); if (SSL) { resTemplateLocal->SetClassName(String::NewFromUtf8(isolate, "uWS.SSLHttpResponse", NewStringType::kNormal).ToLocalChecked()); @@ -263,13 +273,7 @@ struct HttpResponseWrapper { /* Create our template */ Local resObjectLocal = resTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); - resTemplate[SSL].Reset(isolate, resObjectLocal); - } - - template - static Local getResInstance() { - return Local::New(isolate, resTemplate[std::is_same::value])->Clone(); + + return resObjectLocal; } }; - -Persistent HttpResponseWrapper::resTemplate[2]; diff --git a/src/Utilities.h b/src/Utilities.h index 9c5f998..256591a 100644 --- a/src/Utilities.h +++ b/src/Utilities.h @@ -4,6 +4,23 @@ #include using namespace v8; +struct PerContextData { + Isolate *isolate; + UniquePersistent reqTemplate; + UniquePersistent resTemplate[2]; + UniquePersistent wsTemplate[2]; + + /* We hold all apps until free */ + std::vector> apps; + std::vector> sslApps; +}; + +template +static constexpr int getAppTypeIndex() { + /* Returns 1 for SSLApp and 0 for App */ + return std::is_same::value; +} + class NativeString { char *data; size_t length; @@ -36,7 +53,7 @@ public: bool isInvalid(const FunctionCallbackInfo &args) { if (invalid) { - args.GetReturnValue().Set(isolate->ThrowException(String::NewFromUtf8(isolate, "Text and data can only be passed by String, ArrayBuffer or TypedArray.", NewStringType::kNormal).ToLocalChecked())); + args.GetReturnValue().Set(args.GetIsolate()->ThrowException(String::NewFromUtf8(args.GetIsolate(), "Text and data can only be passed by String, ArrayBuffer or TypedArray.", NewStringType::kNormal).ToLocalChecked())); } return invalid; } diff --git a/src/WebSocketWrapper.h b/src/WebSocketWrapper.h index bc47669..894a945 100644 --- a/src/WebSocketWrapper.h +++ b/src/WebSocketWrapper.h @@ -1,15 +1,16 @@ #include "App.h" -#include #include "Utilities.h" + +#include using namespace v8; /* todo: probably isCorked, cork should be exposed? */ struct WebSocketWrapper { - static Persistent wsTemplate[2]; template static inline uWS::WebSocket *getWebSocket(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = (uWS::WebSocket *) args.Holder()->GetAlignedPointerFromInternalField(0); if (!ws) { args.GetReturnValue().Set(isolate->ThrowException(String::NewFromUtf8(isolate, "Invalid access of closed uWS.WebSocket/SSLWebSocket.", NewStringType::kNormal).ToLocalChecked())); @@ -24,6 +25,7 @@ struct WebSocketWrapper { /* Takes string topic */ template static void uWS_WebSocket_subscribe(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { NativeString topic(isolate, args[0]); @@ -37,6 +39,7 @@ struct WebSocketWrapper { /* Takes string topic, returns boolean success */ template static void uWS_WebSocket_unsubscribe(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { NativeString topic(isolate, args[0]); @@ -51,6 +54,7 @@ struct WebSocketWrapper { /* Takes string topic, message */ template static void uWS_WebSocket_publish(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { NativeString topic(isolate, args[0]); @@ -75,6 +79,7 @@ struct WebSocketWrapper { /* Takes code, message, returns undefined */ template static void uWS_WebSocket_end(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { int code = 0; @@ -95,6 +100,7 @@ struct WebSocketWrapper { /* Takes nothing returns arraybuffer */ template static void uWS_WebSocket_getRemoteAddress(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { std::string_view ip = ws->getRemoteAddress(); @@ -107,6 +113,7 @@ struct WebSocketWrapper { /* Takes nothing, returns integer */ template static void uWS_WebSocket_getBufferedAmount(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { int bufferedAmount = ws->getBufferedAmount(); @@ -117,6 +124,7 @@ struct WebSocketWrapper { /* Takes message, isBinary. Returns true on success, false otherwise */ template static void uWS_WebSocket_send(const FunctionCallbackInfo &args) { + Isolate *isolate = args.GetIsolate(); auto *ws = getWebSocket(args); if (ws) { NativeString message(args.GetIsolate(), args[0]); @@ -131,7 +139,7 @@ struct WebSocketWrapper { } template - static void initWsTemplate() { + static Local init(Isolate *isolate) { Local wsTemplateLocal = FunctionTemplate::New(isolate); if (SSL) { wsTemplateLocal->SetClassName(String::NewFromUtf8(isolate, "uWS.SSLWebSocket", NewStringType::kNormal).ToLocalChecked()); @@ -152,15 +160,7 @@ struct WebSocketWrapper { /* Create the template */ Local wsObjectLocal = wsTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked(); - wsTemplate[SSL].Reset(isolate, wsObjectLocal); - } - - /* This is where we output an instance */ - template - static Local getWsInstance() { - return Local::New(isolate, wsTemplate[std::is_same::value])->Clone(); + + return wsObjectLocal; } }; - -/* Fix this, should be nicer */ -Persistent WebSocketWrapper::wsTemplate[2]; diff --git a/src/addon.cpp b/src/addon.cpp index 646df04..332813f 100644 --- a/src/addon.cpp +++ b/src/addon.cpp @@ -17,20 +17,14 @@ /* We are only allowed to depend on µWS and V8 in this layer. */ #include "App.h" -#include + #include #include #include + +#include using namespace v8; -/* These two are definitely static */ -Isolate *isolate; -bool valid = true; - -/* We hold all apps until free */ -std::vector> apps; -std::vector> sslApps; - /* Compatibility for V8 7.0 and earlier */ #include bool BooleanValue(Isolate *isolate, Local value) { @@ -53,13 +47,27 @@ bool BooleanValue(Isolate *isolate, Local value) { /* This has to be called in beforeExit, but exit also seems okay */ void uWS_free(const FunctionCallbackInfo &args) { - if (valid) { + + /* We get the External holding perContextData */ + PerContextData *perContextData = (PerContextData *) Local::Cast(args.Data())->Value(); + + /* Todo: this will always be true */ + if (perContextData) { /* Freeing apps here, it could be done earlier but not sooner */ - apps.clear(); - sslApps.clear(); + perContextData->apps.clear(); + perContextData->sslApps.clear(); /* Freeing the loop here means we give time for our timers to close, etc */ uWS::Loop::get()->free(); - valid = false; + + + // we need to mark this + delete perContextData; + + // we can override the exports->free function to null after! + + //args.Data() + + //Local::Cast(args.Data())-> } } @@ -69,23 +77,31 @@ void uWS_us_listen_socket_close(const FunctionCallbackInfo &args) { us_listen_socket_close(0, (struct us_listen_socket_t *) External::Cast(*args[0])->Value()); } -#include - void Main(Local exports) { - /* I guess we store this statically */ - isolate = exports->GetIsolate(); + + /* We pass isolate everywhere */ + Isolate *isolate = exports->GetIsolate(); /* We want this so that we can redefine process.nextTick to using the V8 native microtask queue */ // todo: setting this might be crashing nodejs? isolate->SetMicrotasksPolicy(MicrotasksPolicy::kAuto); - /* Integrate with existing libuv loop, we just pass a boolean basically */ - uWS::Loop::get(uv_default_loop()); + /* Init the template objects, SSL and non-SSL, store it in per context data */ + PerContextData *perContextData = new PerContextData; + perContextData->isolate = isolate; + perContextData->reqTemplate.Reset(isolate, HttpRequestWrapper::init(isolate)); + perContextData->resTemplate[0].Reset(isolate, HttpResponseWrapper::init<0>(isolate)); + perContextData->resTemplate[1].Reset(isolate, HttpResponseWrapper::init<1>(isolate)); + perContextData->wsTemplate[0].Reset(isolate, WebSocketWrapper::init<0>(isolate)); + perContextData->wsTemplate[1].Reset(isolate, WebSocketWrapper::init<1>(isolate)); + + /* Refer to per context data via External */ + Local externalPerContextData = External::New(isolate, perContextData); /* uWS namespace */ - exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "App", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); - exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "SSLApp", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); - exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "free", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_free)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); + exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "App", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App, externalPerContextData)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); + exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "SSLApp", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_App, externalPerContextData)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); + exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "free", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_free, externalPerContextData)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); /* Expose some µSockets functions directly under uWS namespace */ exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "us_listen_socket_close", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, uWS_us_listen_socket_close)->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()).ToChecked(); @@ -97,21 +113,16 @@ void Main(Local exports) { /* Listen options */ exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "LIBUS_LISTEN_EXCLUSIVE_PORT", NewStringType::kNormal).ToLocalChecked(), Integer::NewFromUnsigned(isolate, LIBUS_LISTEN_EXCLUSIVE_PORT)).ToChecked(); - - /* The template for websockets */ - WebSocketWrapper::initWsTemplate<0>(); - WebSocketWrapper::initWsTemplate<1>(); - - /* Initialize SSL and non-SSL templates */ - HttpResponseWrapper::initResTemplate<0>(); - HttpResponseWrapper::initResTemplate<1>(); - - /* Init a shared request object */ - HttpRequestWrapper::initReqTemplate(); } /* This is required when building as a Node.js addon */ #ifndef ADDON_IS_HOST #include -NODE_MODULE(uWS, Main) -#endif +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(Local exports, Local module, Local context) { + /* Integrate uSockets with existing libuv loop */ + uWS::Loop::get(node::GetCurrentEventLoop(context->GetIsolate())); + /* Register vanilla V8 addon */ + Main(exports); +} +#endif \ No newline at end of file diff --git a/uWebSockets b/uWebSockets index f358601..02ad397 160000 --- a/uWebSockets +++ b/uWebSockets @@ -1 +1 @@ -Subproject commit f358601505374371f24f8e5b50f630a5646bc682 +Subproject commit 02ad3979a61cc4df9c15f2c776a7bbe2ba6dd09e