Skip to content

Commit 6eac60e

Browse files
authored
doc: add example and rewrite readme (#214)
1 parent 81897a8 commit 6eac60e

15 files changed

Lines changed: 816 additions & 776 deletions

File tree

packages/emnapi/README.md

Lines changed: 164 additions & 776 deletions
Large diffs are not rendered by default.

packages/emnapi/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ exports.requiredConfig = {
4646
'_node_api_module_get_api_version_v1',
4747
'_emnapi_create_env',
4848
'_emnapi_delete_env',
49+
'_uv_library_shutdown'
4950
],
5051
},
5152
},
@@ -65,6 +66,7 @@ exports.requiredConfig = {
6566
'--export=emnapi_thread_crashed',
6667
'--export-if-defined=emnapi_async_worker_create',
6768
'--export-if-defined=emnapi_async_worker_init',
69+
'--export-if-defined=uv_library_shutdown',
6870
'--export=emnapi_create_env',
6971
'--export=emnapi_delete_env',
7072
],

packages/example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
!node_modules
2+
out

packages/example/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
```bash
2+
cd ../..
3+
npm install
4+
npm run build
5+
node ./script/release.js
6+
7+
cd ./packages/example
8+
chmod +x ./build.sh
9+
./build.sh
10+
11+
node ./index-emscripten.js
12+
node ./index-wasi.js
13+
```

packages/example/binding.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#include <node_api.h>
2+
#include <stdlib.h>
3+
#include <time.h>
4+
#include <pthread.h>
5+
6+
typedef struct {
7+
int current;
8+
int total;
9+
} run_context_t;
10+
11+
typedef struct {
12+
napi_async_work work;
13+
napi_threadsafe_function tsfn;
14+
napi_ref done_ref;
15+
pthread_mutex_t mutex;
16+
int ref_count;
17+
run_context_t context;
18+
} run_data_t;
19+
20+
static void run_data_unref(run_data_t* d) {
21+
pthread_mutex_lock(&d->mutex);
22+
int remaining = --d->ref_count;
23+
pthread_mutex_unlock(&d->mutex);
24+
if (remaining == 0) {
25+
pthread_mutex_destroy(&d->mutex);
26+
free(d);
27+
}
28+
}
29+
30+
static void tsfn_callback(napi_env env, napi_value js_callback,
31+
void* context, void* data) {
32+
(void)data;
33+
if (js_callback == NULL) return;
34+
35+
napi_value undefined;
36+
napi_get_undefined(env, &undefined);
37+
38+
napi_value argv[2];
39+
run_context_t* ctx = (run_context_t*)context;
40+
napi_create_int32(env, ctx->current, &argv[0]);
41+
napi_create_int32(env, ctx->total, &argv[1]);
42+
napi_call_function(env, undefined, js_callback, 2, argv, NULL);
43+
}
44+
45+
static void tsfn_finalize_cb(napi_env env, void* finalize_data,
46+
void* finalize_hint) {
47+
(void)finalize_hint;
48+
run_data_t* run_data = (run_data_t*)finalize_data;
49+
50+
if (run_data->done_ref != NULL) {
51+
napi_value done_fn;
52+
napi_get_reference_value(env, run_data->done_ref, &done_fn);
53+
if (done_fn != NULL) {
54+
napi_value undefined;
55+
napi_get_undefined(env, &undefined);
56+
napi_call_function(env, undefined, done_fn, 0, NULL, NULL);
57+
}
58+
napi_delete_reference(env, run_data->done_ref);
59+
run_data->done_ref = NULL;
60+
}
61+
62+
run_data_unref(run_data);
63+
}
64+
65+
static void execute(napi_env env, void* data) {
66+
run_data_t* run_data = (run_data_t*)data;
67+
68+
struct timespec ts;
69+
ts.tv_sec = 0;
70+
ts.tv_nsec = 200 * 1000 * 1000; /* 200ms */
71+
72+
for (int i = 0; i < 5; i++) {
73+
nanosleep(&ts, NULL);
74+
run_data->context.current++;
75+
napi_status status = napi_call_threadsafe_function(
76+
run_data->tsfn, NULL, napi_tsfn_blocking);
77+
if (status != napi_ok) break;
78+
}
79+
80+
napi_release_threadsafe_function(run_data->tsfn, napi_tsfn_release);
81+
}
82+
83+
static void complete(napi_env env, napi_status status, void* data) {
84+
run_data_t* run_data = (run_data_t*)data;
85+
napi_delete_async_work(env, run_data->work);
86+
run_data_unref(run_data);
87+
}
88+
89+
static napi_value run(napi_env env, napi_callback_info info) {
90+
size_t argc = 2;
91+
napi_value argv[2];
92+
napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
93+
94+
if (argc < 1) {
95+
napi_throw_type_error(env, NULL, "run expects a callback argument");
96+
return NULL;
97+
}
98+
99+
napi_valuetype vt;
100+
napi_typeof(env, argv[0], &vt);
101+
if (vt != napi_function) {
102+
napi_throw_type_error(env, NULL, "first argument must be a function");
103+
return NULL;
104+
}
105+
106+
napi_ref done_ref = NULL;
107+
if (argc >= 2) {
108+
napi_valuetype vt2;
109+
napi_typeof(env, argv[1], &vt2);
110+
if (vt2 != napi_function) {
111+
napi_throw_type_error(env, NULL, "second argument must be a function");
112+
return NULL;
113+
}
114+
napi_create_reference(env, argv[1], 1, &done_ref);
115+
}
116+
117+
run_data_t* run_data = (run_data_t*)malloc(sizeof(run_data_t));
118+
if (run_data == NULL) {
119+
if (done_ref != NULL) napi_delete_reference(env, done_ref);
120+
napi_throw_error(env, NULL, "malloc failed");
121+
return NULL;
122+
}
123+
run_data->done_ref = done_ref;
124+
run_data->ref_count = 2;
125+
run_data->context.current = 0;
126+
run_data->context.total = 5;
127+
pthread_mutex_init(&run_data->mutex, NULL);
128+
129+
napi_value async_name;
130+
napi_create_string_utf8(env, "run_tsfn", NAPI_AUTO_LENGTH, &async_name);
131+
132+
napi_status status = napi_create_threadsafe_function(
133+
env,
134+
argv[0], /* js_func */
135+
NULL, /* async_resource */
136+
async_name, /* async_resource_name */
137+
0, /* max_queue_size: 0 = unlimited */
138+
1, /* initial_thread_count */
139+
run_data, /* thread_finalize_data */
140+
tsfn_finalize_cb, /* thread_finalize_cb */
141+
&run_data->context, /* context */
142+
tsfn_callback, /* call_js_cb */
143+
&run_data->tsfn);
144+
145+
if (status != napi_ok) {
146+
pthread_mutex_destroy(&run_data->mutex);
147+
free(run_data);
148+
napi_throw_error(env, NULL, "napi_create_threadsafe_function failed");
149+
return NULL;
150+
}
151+
152+
napi_value work_name;
153+
napi_create_string_utf8(env, "run_work", NAPI_AUTO_LENGTH, &work_name);
154+
155+
status = napi_create_async_work(env, NULL, work_name,
156+
execute, complete,
157+
run_data, &run_data->work);
158+
if (status != napi_ok) {
159+
napi_release_threadsafe_function(run_data->tsfn, napi_tsfn_release);
160+
napi_throw_error(env, NULL, "napi_create_async_work failed");
161+
return NULL;
162+
}
163+
164+
napi_queue_async_work(env, run_data->work);
165+
166+
napi_value undefined;
167+
napi_get_undefined(env, &undefined);
168+
return undefined;
169+
}
170+
171+
NAPI_MODULE_INIT() {
172+
napi_value fn;
173+
napi_create_function(env, "run", NAPI_AUTO_LENGTH, run, NULL, &fn);
174+
napi_set_named_property(env, exports, "run", fn);
175+
return exports;
176+
}

packages/example/binding.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#include <napi.h>
2+
#include <time.h>
3+
#include <pthread.h>
4+
5+
struct RunContext {
6+
int current;
7+
int total;
8+
};
9+
10+
struct RunData {
11+
Napi::ThreadSafeFunction tsfn;
12+
Napi::FunctionReference done_ref;
13+
pthread_mutex_t mutex;
14+
int ref_count;
15+
RunContext context;
16+
17+
RunData() : ref_count(2), context{0, 5} {
18+
pthread_mutex_init(&mutex, NULL);
19+
}
20+
21+
~RunData() { pthread_mutex_destroy(&mutex); }
22+
23+
void unref() {
24+
pthread_mutex_lock(&mutex);
25+
int remaining = --ref_count;
26+
pthread_mutex_unlock(&mutex);
27+
if (remaining == 0) delete this;
28+
}
29+
};
30+
31+
class RunWorker : public Napi::AsyncWorker {
32+
public:
33+
explicit RunWorker(Napi::Env env, RunData* data)
34+
: Napi::AsyncWorker(env, "run_work"), data_(data) {}
35+
36+
void Execute() override {
37+
struct timespec ts;
38+
ts.tv_sec = 0;
39+
ts.tv_nsec = 200 * 1000 * 1000; // 200ms
40+
41+
for (int i = 0; i < 5; i++) {
42+
nanosleep(&ts, nullptr);
43+
RunContext* ctx = data_->tsfn.GetContext();
44+
int cur = ++ctx->current;
45+
int tot = ctx->total;
46+
47+
napi_status status = data_->tsfn.BlockingCall(
48+
[cur, tot](Napi::Env env, Napi::Function jsCb) {
49+
jsCb.Call({Napi::Number::New(env, cur),
50+
Napi::Number::New(env, tot)});
51+
});
52+
if (status != napi_ok) break;
53+
}
54+
55+
data_->tsfn.Release();
56+
}
57+
58+
void OnOK() override { data_->unref(); }
59+
void OnError(const Napi::Error& /*e*/) override { data_->unref(); }
60+
61+
private:
62+
RunData* data_;
63+
};
64+
65+
static Napi::Value Run(const Napi::CallbackInfo& info) {
66+
Napi::Env env = info.Env();
67+
68+
if (info.Length() < 1 || !info[0].IsFunction()) {
69+
Napi::TypeError::New(env, "first argument must be a function")
70+
.ThrowAsJavaScriptException();
71+
return env.Undefined();
72+
}
73+
74+
RunData* data = new RunData();
75+
76+
if (info.Length() >= 2) {
77+
if (!info[1].IsFunction()) {
78+
delete data;
79+
Napi::TypeError::New(env, "second argument must be a function")
80+
.ThrowAsJavaScriptException();
81+
return env.Undefined();
82+
}
83+
data->done_ref = Napi::Persistent(info[1].As<Napi::Function>());
84+
}
85+
86+
data->tsfn = Napi::ThreadSafeFunction::New(
87+
env,
88+
info[0].As<Napi::Function>(),
89+
"run_tsfn",
90+
0,
91+
1,
92+
&data->context,
93+
[](Napi::Env env, RunData* finalizeData, RunContext* ctx) {
94+
if (!finalizeData->done_ref.IsEmpty()) {
95+
finalizeData->done_ref.Call({});
96+
finalizeData->done_ref.Reset();
97+
}
98+
finalizeData->unref();
99+
},
100+
data);
101+
102+
auto* worker = new RunWorker(env, data);
103+
worker->Queue();
104+
105+
return env.Undefined();
106+
}
107+
108+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
109+
exports.Set("run", Napi::Function::New(env, Run));
110+
return exports;
111+
}
112+
113+
NODE_API_MODULE(binding, Init)

0 commit comments

Comments
 (0)