Skip to content

Commit b869320

Browse files
committed
use the C++ api instead of napi (which can be slow)
1 parent 5ef9e84 commit b869320

6 files changed

Lines changed: 377 additions & 29 deletions

File tree

binding.gyp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,21 @@
99
"-mtls-dialect=gnu2",
1010
"-fPIC",
1111
"-O3",
12-
"-g"
12+
"-g",
1313
]
1414
}],
1515
['target_arch == "arm64"', {
1616
"cflags": [
1717
"-ftls-model=local-dynamic",
1818
"-fPIC",
1919
"-O3",
20-
"-g"
20+
"-g",
2121
]
2222
}],
2323
],
2424
"sources": [
25-
"js/addon.c",
26-
"js/addon_node.c",
27-
"src/customlabels.c",
25+
"js/addon2.cpp",
26+
"src/customlabels.cpp",
2827
"src/hashmap.c"
2928
],
3029
}

js/addon2.cpp

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// addon.c
2+
// #include "addon.h"
3+
#include "../src/customlabels.h"
4+
#include "../src/hashmap.h"
5+
// #include "js_native_api.h"
6+
7+
#include <node.h>
8+
9+
#include <assert.h>
10+
#include <errno.h>
11+
#include <stdbool.h>
12+
#include <stdint.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
16+
namespace custom_labels {
17+
using v8::FunctionCallbackInfo;
18+
using v8::Isolate;
19+
using v8::Local;
20+
using v8::NewStringType;
21+
using v8::Object;
22+
using v8::String;
23+
using v8::Value;
24+
25+
__thread custom_labels_hashmap_t *custom_labels_async_hashmap;
26+
27+
#define hm custom_labels_async_hashmap
28+
29+
#define hm_alloc custom_labels_hm_alloc
30+
#define hm_free custom_labels_hm_free
31+
#define hm_insert custom_labels_hm_insert
32+
#define hm_get custom_labels_hm_get
33+
#define hm_delete custom_labels_hm_delete
34+
35+
typedef struct {
36+
custom_labels_labelset_t *ls;
37+
unsigned refs;
38+
} labelset_rc;
39+
40+
static void init() { hm = hm_alloc(); }
41+
42+
typedef enum {
43+
SUCCESS,
44+
ALLOC_FAILED,
45+
CHILD_ALREADY_EXISTED,
46+
} hm_error_t;
47+
48+
const char *my_strerror(hm_error_t err) {
49+
switch (err) {
50+
case SUCCESS:
51+
return "(no error)";
52+
case ALLOC_FAILED:
53+
return "allocation failed";
54+
case CHILD_ALREADY_EXISTED:
55+
return "child already existed";
56+
}
57+
return "(unknown error)";
58+
}
59+
60+
static void unref(labelset_rc *rc) {
61+
if (rc && !--rc->refs) {
62+
// TODO why do we need two allocations here? Like why can't the labelset
63+
// just be inline in the RC?
64+
custom_labels_free(rc->ls);
65+
free(rc);
66+
}
67+
}
68+
69+
static hm_error_t ensure_init() {
70+
if (!hm) [[unlikely]]
71+
init();
72+
if (!hm) [[unlikely]]
73+
return ALLOC_FAILED;
74+
return SUCCESS;
75+
}
76+
77+
static hm_error_t propagate(uint64_t parent, uint64_t child) {
78+
hm_error_t error;
79+
if ((error = ensure_init())) [[unlikely]]
80+
return error;
81+
labelset_rc *parent_rc = (labelset_rc *)hm_get(hm, parent);
82+
if (parent_rc && custom_labels_count(parent_rc->ls)) {
83+
++parent_rc->refs;
84+
labelset_rc *old;
85+
bool success = hm_insert(hm, child, parent_rc, (void **)&old);
86+
if (!success) [[unlikely]] {
87+
return ALLOC_FAILED;
88+
}
89+
if (old) [[unlikely]] {
90+
unref(old);
91+
return CHILD_ALREADY_EXISTED;
92+
}
93+
}
94+
return SUCCESS;
95+
}
96+
97+
static int64_t val2int(const Local<Value> val) {
98+
// see napi_get_value_int64
99+
if (val->IsInt32()) [[likely]] {
100+
return val.As<v8::Int32>()->Value();
101+
}
102+
v8::Local<v8::Context> context;
103+
return val->IntegerValue(context).FromJust();
104+
}
105+
106+
static void throw_str(Isolate *isolate, char const *c_str) {
107+
// The V2 API is only available in very recent Node versions.
108+
auto str =
109+
v8::String::NewFromUtf8(isolate, c_str, v8::NewStringType::kInternalized);
110+
v8::Local<v8::Value> error_obj = v8::Exception::Error(str.ToLocalChecked());
111+
isolate->ThrowException(error_obj);
112+
}
113+
114+
static void throw_error(Isolate *isolate, hm_error_t err) {
115+
throw_str(isolate, my_strerror(err));
116+
}
117+
118+
static hm_error_t destroy(uint64_t id) {
119+
hm_error_t error;
120+
if ((error = ensure_init())) [[unlikely]]
121+
return error;
122+
labelset_rc *rc = (labelset_rc *)hm_delete(hm, id);
123+
unref(rc);
124+
return SUCCESS;
125+
}
126+
127+
static void Destroy(const FunctionCallbackInfo<Value> &args) {
128+
Isolate *isolate = args.GetIsolate();
129+
uint64_t async_id = val2int(args[0]);
130+
hm_error_t err = destroy(async_id);
131+
if (err) [[unlikely]] {
132+
throw_error(isolate, err);
133+
return;
134+
}
135+
}
136+
137+
static void Propagate(const FunctionCallbackInfo<Value> &args) {
138+
Isolate *isolate = args.GetIsolate();
139+
uint64_t parent_id = val2int(args[0]);
140+
uint64_t child_id = val2int(args[1]);
141+
hm_error_t err = propagate(parent_id, child_id);
142+
if (err) [[unlikely]] {
143+
throw_error(isolate, err);
144+
return;
145+
}
146+
}
147+
148+
#define MAX_LABELS 10
149+
#define MAX_KEY_SIZE 16
150+
#define MAX_VAL_SIZE 48
151+
152+
#define STR_HELPER(x) #x
153+
#define STR(x) STR_HELPER(x)
154+
155+
static hm_error_t reify(uint64_t async_id, uint64_t capacity,
156+
custom_labels_labelset_t **out) {
157+
assert(out); // Justification: this can't be called from JS
158+
// TODO: should the HM have a get_or_insert function ?
159+
labelset_rc *rc = (labelset_rc *)hm_get(hm, async_id);
160+
*out = NULL;
161+
if (!rc) {
162+
rc = (labelset_rc *)malloc(sizeof *rc);
163+
if (!rc) [[unlikely]]
164+
return ALLOC_FAILED;
165+
rc->refs = 1;
166+
rc->ls = *out = custom_labels_new(capacity);
167+
if (!*out) [[unlikely]] {
168+
unref(rc);
169+
return ALLOC_FAILED;
170+
}
171+
if (!hm_insert(hm, async_id, rc, NULL)) [[unlikely]] {
172+
unref(rc);
173+
return ALLOC_FAILED;
174+
}
175+
} else if (rc->refs > 1) {
176+
labelset_rc *new_rc = (labelset_rc *)malloc(sizeof *rc);
177+
if (!new_rc) [[unlikely]] {
178+
return ALLOC_FAILED;
179+
}
180+
new_rc->refs = 1;
181+
new_rc->ls = *out = custom_labels_clone(rc->ls);
182+
if (!*out) [[unlikely]] {
183+
unref(new_rc);
184+
return ALLOC_FAILED;
185+
}
186+
if (!hm_insert(hm, async_id, new_rc, NULL)) [[unlikely]] {
187+
unref(new_rc);
188+
return ALLOC_FAILED;
189+
}
190+
--rc->refs;
191+
} else {
192+
*out = rc->ls;
193+
}
194+
return SUCCESS;
195+
}
196+
197+
static void WithLabelsInternal(const FunctionCallbackInfo<Value> &args) {
198+
Isolate *isolate = args.GetIsolate();
199+
ensure_init();
200+
const int max_args = MAX_LABELS * 2 + 2;
201+
const int argc = args.Length();
202+
hm_error_t err;
203+
Local<v8::Context> context = isolate->GetCurrentContext();
204+
Local<Object> global = context->Global();
205+
Local<v8::Function> func;
206+
v8::MaybeLocal<Value> maybe;
207+
208+
if (argc > max_args) [[unlikely]] {
209+
throw_str(isolate, "max " STR(MAX_LABELS) " labels per call");
210+
return;
211+
}
212+
if (argc < 2 || argc % 2) [[unlikely]] {
213+
throw_str(isolate, "withLabels(f, k, v, ...)");
214+
return;
215+
}
216+
if (!args[1]->IsFunction()) [[unlikely]] {
217+
throw_str(isolate, "withLabels(f, k, v, ...)");
218+
return;
219+
}
220+
func = args[1].As<v8::Function>();
221+
uint64_t async_id = val2int(args[0]);
222+
size_t n_labels = (argc - 2) / 2;
223+
custom_labels_label_t *labels =
224+
(custom_labels_label_t *)malloc(n_labels * sizeof *labels);
225+
size_t i;
226+
Local<Value> key, val;
227+
Local<v8::String> key_str, val_str;
228+
for (i = 0; i < n_labels; ++i) {
229+
labels[i].key.buf = (const unsigned char *)malloc(MAX_KEY_SIZE);
230+
labels[i].value.buf = (const unsigned char *)malloc(MAX_VAL_SIZE);
231+
if (!labels[i].key.buf || !labels[i].value.buf) [[unlikely]] {
232+
free((void *)labels[i].key.buf);
233+
free((void *)labels[i].value.buf);
234+
throw_str(isolate, "alloc failed");
235+
goto cleanup;
236+
}
237+
key = args[2 + 2 * i];
238+
if (!key->IsString()) [[unlikely]] {
239+
free((void *)labels[i].key.buf);
240+
free((void *)labels[i].value.buf);
241+
throw_str(isolate, "key and value must be strings");
242+
goto cleanup;
243+
}
244+
key_str = key.As<v8::String>();
245+
labels[i].key.len = key_str->WriteUtf8(
246+
isolate, (char *)labels[i].key.buf, MAX_KEY_SIZE, nullptr,
247+
v8::String::WriteOptions::NO_NULL_TERMINATION |
248+
v8::String::WriteOptions::REPLACE_INVALID_UTF8);
249+
250+
val = args[3 + 2 * i];
251+
if (!val->IsString()) [[unlikely]] {
252+
free((void *)labels[i].key.buf);
253+
free((void *)labels[i].value.buf);
254+
throw_str(isolate, "key and value must be strings");
255+
goto cleanup;
256+
}
257+
val_str = val.As<v8::String>();
258+
labels[i].value.len = val_str->WriteUtf8(
259+
isolate, (char *)labels[i].value.buf, MAX_VAL_SIZE, nullptr,
260+
v8::String::WriteOptions::NO_NULL_TERMINATION |
261+
v8::String::WriteOptions::REPLACE_INVALID_UTF8);
262+
}
263+
264+
custom_labels_labelset_t *ls;
265+
err = reify(async_id, n_labels, &ls);
266+
if (err) [[unlikely]] {
267+
throw_str(isolate, "alloc failed");
268+
goto cleanup;
269+
}
270+
271+
for (size_t i = 0; i < n_labels; ++i) {
272+
custom_labels_string_t old_val;
273+
int error =
274+
custom_labels_careful_set(ls, labels[i].key, labels[i].value, &old_val);
275+
free((void *)labels[i].value.buf);
276+
labels[i].value = old_val;
277+
if (error) [[unlikely]] {
278+
throw_str(isolate, "alloc failed");
279+
goto cleanup;
280+
}
281+
}
282+
283+
maybe = func->Call(context, global, 0, nullptr);
284+
if (maybe.IsEmpty()) [[unlikely]] {
285+
throw_str(isolate, "unexpected error!");
286+
goto cleanup;
287+
}
288+
args.GetReturnValue().Set(maybe.ToLocalChecked());
289+
290+
err = reify(async_id, 0, &ls);
291+
if (err) [[unlikely]] {
292+
throw_str(isolate, "alloc failed");
293+
goto cleanup;
294+
}
295+
296+
for (size_t i = 0; i < n_labels; ++i) {
297+
int error = 0;
298+
if (labels[i].value.buf)
299+
error =
300+
custom_labels_careful_set(ls, labels[i].key, labels[i].value, NULL);
301+
else
302+
custom_labels_careful_delete(ls, labels[i].key);
303+
if (error) [[unlikely]] {
304+
err = ALLOC_FAILED;
305+
break;
306+
}
307+
}
308+
if (err) [[unlikely]] {
309+
throw_error(isolate, err);
310+
}
311+
312+
cleanup:
313+
for (size_t j = 0; j < i; ++j) {
314+
free((void *)labels[j].key.buf);
315+
free((void *)labels[j].value.buf);
316+
}
317+
free(labels);
318+
}
319+
320+
#pragma GCC diagnostic push
321+
#pragma GCC diagnostic ignored "-Wcast-function-type"
322+
323+
NODE_MODULE_INIT() {
324+
NODE_SET_METHOD(exports, "withLabelsInternal", WithLabelsInternal);
325+
NODE_SET_METHOD(exports, "propagate", Propagate);
326+
NODE_SET_METHOD(exports, "destroy", Destroy);
327+
}
328+
} // namespace custom_labels
329+
330+
#pragma GCC diagnostic pop

0 commit comments

Comments
 (0)