Skip to content

Commit 3e2ea85

Browse files
Replace NativeString heap allocations with thread-local pool allocator (#1263)
* Initial plan * Update NativeString callers to use new NativeStringContext parameter Add NativeStringContext nativeStringContext; as the first local variable in each function containing NativeString constructions, and pass it as the first argument to each NativeString constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: uNetworkingAB <110806833+uNetworkingAB@users.noreply.github.com> * Add NativeStringContext and update NativeString to use pool allocation in Utilities.h Agent-Logs-Url: https://github.com/uNetworking/uWebSockets.js/sessions/0a6be420-47f1-4fb3-a5d5-55f6d09eb1b5 Co-authored-by: uNetworkingAB <110806833+uNetworkingAB@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: uNetworkingAB <110806833+uNetworkingAB@users.noreply.github.com>
1 parent 6b4968f commit 3e2ea85

6 files changed

Lines changed: 124 additions & 75 deletions

File tree

src/AppWrapper.h

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ void uWS_App_ws(const FunctionCallbackInfo<Value> &args) {
3737
/* This one is default constructed with defaults */
3838
typename APP::template WebSocketBehavior<PerSocketData> behavior = {};
3939

40-
NativeString pattern(args.GetIsolate(), args[0]);
40+
NativeStringContext nativeStringContext;
41+
NativeString pattern(nativeStringContext, args.GetIsolate(), args[0]);
4142
if (pattern.isInvalid(args)) {
4243
return;
4344
}
@@ -313,8 +314,9 @@ template <typename APP, typename F>
313314
void uWS_App_get(F f, const FunctionCallbackInfo<Value> &args) {
314315
APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0);
315316

317+
NativeStringContext nativeStringContext;
316318
/* Pattern */
317-
NativeString pattern(args.GetIsolate(), args[0]);
319+
NativeString pattern(nativeStringContext, args.GetIsolate(), args[0]);
318320
if (pattern.isInvalid(args)) {
319321
return;
320322
}
@@ -328,7 +330,7 @@ void uWS_App_get(F f, const FunctionCallbackInfo<Value> &args) {
328330

329331
/* If the handler is String */
330332
if (args[1]->IsArrayBuffer()) {
331-
NativeString constantString(args.GetIsolate(), args[1]);
333+
NativeString constantString(nativeStringContext, args.GetIsolate(), args[1]);
332334
if (constantString.isInvalid(args)) {
333335
return;
334336
}
@@ -513,7 +515,8 @@ void uWS_App_listen_unix(const FunctionCallbackInfo<Value> &args) {
513515

514516
/* Path is last */
515517
std::string path;
516-
NativeString h(isolate, args[args.Length() - 1]);
518+
NativeStringContext nativeStringContext;
519+
NativeString h(nativeStringContext, isolate, args[args.Length() - 1]);
517520
if (h.isInvalid(args)) {
518521
return;
519522
}
@@ -545,8 +548,9 @@ void uWS_App_listen(const FunctionCallbackInfo<Value> &args) {
545548

546549
/* Host is first, if present */
547550
std::string host;
551+
NativeStringContext nativeStringContext;
548552
if (!args[0]->IsNumber()) {
549-
NativeString h(isolate, args[0]);
553+
NativeString h(nativeStringContext, isolate, args[0]);
550554
if (h.isInvalid(args)) {
551555
return;
552556
}
@@ -605,7 +609,8 @@ void uWS_App_domain(const FunctionCallbackInfo<Value> &args) {
605609
return;
606610
}
607611

608-
NativeString serverName(isolate, args[0]);
612+
NativeStringContext nativeStringContext;
613+
NativeString serverName(nativeStringContext, isolate, args[0]);
609614
if (serverName.isInvalid(args)) {
610615
return;
611616
}
@@ -626,12 +631,13 @@ void uWS_App_publish(const FunctionCallbackInfo<Value> &args) {
626631
return;
627632
}
628633

629-
NativeString topic(isolate, args[0]);
634+
NativeStringContext nativeStringContext;
635+
NativeString topic(nativeStringContext, isolate, args[0]);
630636
if (topic.isInvalid(args)) {
631637
return;
632638
}
633639

634-
NativeString message(isolate, args[1]);
640+
NativeString message(nativeStringContext, isolate, args[1]);
635641
if (message.isInvalid(args)) {
636642
return;
637643
}
@@ -652,7 +658,8 @@ void uWS_App_numSubscribers(const FunctionCallbackInfo<Value> &args) {
652658
return;
653659
}
654660

655-
NativeString topic(isolate, args[0]);
661+
NativeStringContext nativeStringContext;
662+
NativeString topic(nativeStringContext, isolate, args[0]);
656663
if (topic.isInvalid(args)) {
657664
return;
658665
}
@@ -675,8 +682,9 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
675682

676683
Local<Object> optionsObject = Local<Object>::Cast(args[index]);
677684

685+
NativeStringContext nativeStringContext;
678686
/* Key file name */
679-
NativeString keyFileNameValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "key_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
687+
NativeString keyFileNameValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "key_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
680688
if (keyFileNameValue.isInvalid(args)) {
681689
return {};
682690
}
@@ -686,7 +694,7 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
686694
}
687695

688696
/* Cert file name */
689-
NativeString certFileNameValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "cert_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
697+
NativeString certFileNameValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "cert_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
690698
if (certFileNameValue.isInvalid(args)) {
691699
return {};
692700
}
@@ -696,7 +704,7 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
696704
}
697705

698706
/* Passphrase */
699-
NativeString passphraseValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "passphrase", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
707+
NativeString passphraseValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "passphrase", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
700708
if (passphraseValue.isInvalid(args)) {
701709
return {};
702710
}
@@ -706,7 +714,7 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
706714
}
707715

708716
/* DH params file name */
709-
NativeString dhParamsFileNameValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "dh_params_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
717+
NativeString dhParamsFileNameValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "dh_params_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
710718
if (dhParamsFileNameValue.isInvalid(args)) {
711719
return {};
712720
}
@@ -716,7 +724,7 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
716724
}
717725

718726
/* CA file name */
719-
NativeString caFileNameValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ca_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
727+
NativeString caFileNameValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ca_file_name", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
720728
if (caFileNameValue.isInvalid(args)) {
721729
return {};
722730
}
@@ -729,7 +737,7 @@ std::pair<uWS::SocketContextOptions, bool> readOptionsObject(const FunctionCallb
729737
options.ssl_prefer_low_memory_usage = optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ssl_prefer_low_memory_usage", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked()->BooleanValue(isolate);
730738

731739
/* ssl_ciphers */
732-
NativeString sslCiphersValue(isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ssl_ciphers", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
740+
NativeString sslCiphersValue(nativeStringContext, isolate, optionsObject->Get(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "ssl_ciphers", NewStringType::kNormal).ToLocalChecked()).ToLocalChecked());
733741
if (sslCiphersValue.isInvalid(args)) {
734742
return {};
735743
}
@@ -824,7 +832,8 @@ void uWS_App_addServerName(const FunctionCallbackInfo<Value> &args) {
824832
APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0);
825833

826834
Isolate *isolate = args.GetIsolate();
827-
NativeString hostnamePatternValue(isolate, args[0]);
835+
NativeStringContext nativeStringContext;
836+
NativeString hostnamePatternValue(nativeStringContext, isolate, args[0]);
828837
if (hostnamePatternValue.isInvalid(args)) {
829838
return;
830839
}
@@ -848,7 +857,8 @@ void uWS_App_removeServerName(const FunctionCallbackInfo<Value> &args) {
848857
APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0);
849858

850859
Isolate *isolate = args.GetIsolate();
851-
NativeString hostnamePatternValue(isolate, args[0]);
860+
NativeStringContext nativeStringContext;
861+
NativeString hostnamePatternValue(nativeStringContext, isolate, args[0]);
852862
if (hostnamePatternValue.isInvalid(args)) {
853863
return;
854864
}
@@ -930,8 +940,9 @@ void uWS_App(const FunctionCallbackInfo<Value> &args) {
930940

931941
APP *app = (APP *) args.This()->GetAlignedPointerFromInternalField(0);
932942

943+
NativeStringContext nativeStringContext;
933944
/* Pattern */
934-
NativeString pattern(args.GetIsolate(), args[0]);
945+
NativeString pattern(nativeStringContext, args.GetIsolate(), args[0]);
935946
if (pattern.isInvalid(args)) {
936947
return;
937948
}

src/HttpRequestWrapper.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ struct HttpRequestWrapper {
6262
template <int QUIC>
6363
static void req_getParameter(const FunctionCallbackInfo<Value> &args) {
6464
Isolate *isolate = args.GetIsolate();
65+
NativeStringContext nativeStringContext;
6566
auto *req = getHttpRequest<QUIC>(args);
6667
if (req) {
6768

@@ -71,7 +72,7 @@ struct HttpRequestWrapper {
7172
int index = args[0]->Uint32Value(isolate->GetCurrentContext()).ToChecked();
7273
parameter = req->getParameter(index);
7374
} else {
74-
NativeString data(args.GetIsolate(), args[0]);
75+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
7576
if (data.isInvalid(args)) {
7677
return;
7778
}
@@ -98,9 +99,10 @@ struct HttpRequestWrapper {
9899
template <int QUIC>
99100
static void req_getHeader(const FunctionCallbackInfo<Value> &args) {
100101
Isolate *isolate = args.GetIsolate();
102+
NativeStringContext nativeStringContext;
101103
auto *req = getHttpRequest<QUIC>(args);
102104
if (req) {
103-
NativeString data(args.GetIsolate(), args[0]);
105+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
104106
if (data.isInvalid(args)) {
105107
return;
106108
}
@@ -152,13 +154,14 @@ struct HttpRequestWrapper {
152154
template <int QUIC>
153155
static void req_getQuery(const FunctionCallbackInfo<Value> &args) {
154156
Isolate *isolate = args.GetIsolate();
157+
NativeStringContext nativeStringContext;
155158
auto *req = getHttpRequest<QUIC>(args);
156159
if (req) {
157160
std::string_view query;
158161

159162
/* Do we have a key argument? */
160163
if (args.Length() == 1) {
161-
NativeString keyString(isolate, args[0]);
164+
NativeString keyString(nativeStringContext, isolate, args[0]);
162165
if (keyString.isInvalid(args)) {
163166
return;
164167
}

src/HttpResponseWrapper.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,8 @@ struct HttpResponseWrapper {
369369
static void res_writeStatus(const FunctionCallbackInfo<Value> &args) {
370370
auto *res = getHttpResponse<SSL>(args);
371371
if (res) {
372-
NativeString data(args.GetIsolate(), args[0]);
372+
NativeStringContext nativeStringContext;
373+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
373374
if (data.isInvalid(args)) {
374375
return;
375376
}
@@ -409,7 +410,8 @@ struct HttpResponseWrapper {
409410
static void res_end(const FunctionCallbackInfo<Value> &args) {
410411
auto *res = getHttpResponse<PROTOCOL>(args);
411412
if (res) {
412-
NativeString data(args.GetIsolate(), args[0]);
413+
NativeStringContext nativeStringContext;
414+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
413415
if (data.isInvalid(args)) {
414416
return;
415417
}
@@ -432,9 +434,10 @@ struct HttpResponseWrapper {
432434
template <int PROTOCOL>
433435
static void res_tryEnd(const FunctionCallbackInfo<Value> &args) {
434436
Isolate *isolate = args.GetIsolate();
437+
NativeStringContext nativeStringContext;
435438
auto *res = getHttpResponse<PROTOCOL>(args);
436439
if (res) {
437-
NativeString data(args.GetIsolate(), args[0]);
440+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
438441
if (data.isInvalid(args)) {
439442
return;
440443
}
@@ -465,9 +468,10 @@ struct HttpResponseWrapper {
465468
template <int PROTOCOL>
466469
static void res_write(const FunctionCallbackInfo<Value> &args) {
467470
Isolate *isolate = args.GetIsolate();
471+
NativeStringContext nativeStringContext;
468472
auto *res = getHttpResponse<PROTOCOL>(args);
469473
if (res) {
470-
NativeString data(args.GetIsolate(), args[0]);
474+
NativeString data(nativeStringContext, args.GetIsolate(), args[0]);
471475
if (data.isInvalid(args)) {
472476
return;
473477
}
@@ -482,13 +486,14 @@ struct HttpResponseWrapper {
482486
template <int PROTOCOL>
483487
static void res_writeHeader(const FunctionCallbackInfo<Value> &args) {
484488
Isolate *isolate = args.GetIsolate();
489+
NativeStringContext nativeStringContext;
485490
auto *res = getHttpResponse<PROTOCOL>(args);
486491
if (res) {
487-
NativeString header(args.GetIsolate(), args[0]);
492+
NativeString header(nativeStringContext, args.GetIsolate(), args[0]);
488493
if (header.isInvalid(args)) {
489494
return;
490495
}
491-
NativeString value(args.GetIsolate(), args[1]);
496+
NativeString value(nativeStringContext, args.GetIsolate(), args[1]);
492497
if (value.isInvalid(args)) {
493498
return;
494499
}
@@ -521,24 +526,25 @@ struct HttpResponseWrapper {
521526
template <int SSL>
522527
static void res_upgrade(const FunctionCallbackInfo<Value> &args) {
523528
Isolate *isolate = args.GetIsolate();
529+
NativeStringContext nativeStringContext;
524530
auto *res = getHttpResponse<SSL>(args);
525531
if (res) {
526532
/* We require exactly 5 arguments */
527533
if (args.Length() != 5) {
528534
return;
529535
}
530536

531-
NativeString secWebSocketKey(args.GetIsolate(), args[1]);
537+
NativeString secWebSocketKey(nativeStringContext, args.GetIsolate(), args[1]);
532538
if (secWebSocketKey.isInvalid(args)) {
533539
return;
534540
}
535541

536-
NativeString secWebSocketProtocol(args.GetIsolate(), args[2]);
542+
NativeString secWebSocketProtocol(nativeStringContext, args.GetIsolate(), args[2]);
537543
if (secWebSocketProtocol.isInvalid(args)) {
538544
return;
539545
}
540546

541-
NativeString secWebSocketExtensions(args.GetIsolate(), args[3]);
547+
NativeString secWebSocketExtensions(nativeStringContext, args.GetIsolate(), args[3]);
542548
if (secWebSocketExtensions.isInvalid(args)) {
543549
return;
544550
}

src/Utilities.h

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <openssl/ssl.h>
2222
#include <openssl/x509.h>
2323
#include <v8.h>
24+
#include <vector>
2425
using namespace v8;
2526

2627
/* Unfortunately we _have_ to depend on Node.js crap */
@@ -118,13 +119,26 @@ struct Callback {
118119
}
119120
};
120121

122+
class NativeStringContext {
123+
inline static thread_local std::vector<char> pool = std::vector<char>(8 * 1024 * 1024);
124+
size_t pool_offset = 0;
125+
public:
126+
char *alloc(size_t size) {
127+
if (pool_offset + size > pool.size()) {
128+
pool.resize(pool_offset + size);
129+
}
130+
char *ptr = pool.data() + pool_offset;
131+
pool_offset += size;
132+
return ptr;
133+
}
134+
};
135+
121136
class NativeString {
122137
char *data;
123138
size_t length;
124-
bool strAllocated = false;
125139
bool invalid = false;
126140
public:
127-
NativeString(Isolate *isolate, const Local<Value> &value) {
141+
NativeString(NativeStringContext &ctx, Isolate *isolate, const Local<Value> &value) {
128142
if (value->IsUndefined()) {
129143
data = nullptr;
130144
length = 0;
@@ -138,15 +152,13 @@ class NativeString {
138152
data = (char *) strView.data8();
139153
} else {
140154
// utf16: copy and convert to utf8
141-
strAllocated = true;
142155
length = string->Utf8LengthV2(isolate);
143-
data = new char[length];
156+
data = ctx.alloc(length);
144157
string->WriteUtf8V2(isolate, data, length);
145158
}
146159
#else // Fallback Node.js < 24
147-
strAllocated = true;
148160
length = string->Utf8Length(isolate);
149-
data = new char[length];
161+
data = ctx.alloc(length);
150162
string->WriteUtf8(isolate, data, length, nullptr, String::WriteOptions::NO_NULL_TERMINATION);
151163
#endif
152164
} else if (value->IsArrayBufferView()) { /* DataView or TypedArray */
@@ -179,12 +191,6 @@ class NativeString {
179191
std::string_view getString() {
180192
return {data, length};
181193
}
182-
183-
~NativeString() {
184-
if (strAllocated) {
185-
delete[] data;
186-
}
187-
}
188194
};
189195

190196
// Utility function to extract raw certificate data

0 commit comments

Comments
 (0)