| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b6903aa commit 350b0ea
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -200,7 +200,7 @@ class BufferFinalizer : private Finalizer { | |||
| 200 | 200 | ~BufferFinalizer() { env()->Unref(); } | |
| 201 | 201 | }; | |
| 202 | 202 | ||
| 203 | - class ThreadSafeFunction : public node::AsyncResource { | ||
| 203 | + class ThreadSafeFunction { | ||
| 204 | 204 | public: | |
| 205 | 205 | ThreadSafeFunction(v8::Local<v8::Function> func, | |
| 206 | 206 | v8::Local<v8::Object> resource, | |
@@ -212,11 +212,12 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 212 | 212 | void* finalize_data_, | |
| 213 | 213 | napi_finalize finalize_cb_, | |
| 214 | 214 | napi_threadsafe_function_call_js call_js_cb_) | |
| 215 | - : AsyncResource(env_->isolate, | ||
| 216 | - resource, | ||
| 217 | - node::Utf8Value(env_->isolate, name).ToStringView()), | ||
| 215 | + : async_resource(std::in_place, | ||
| 216 | + env_->isolate, | ||
| 217 | + resource, | ||
| 218 | + node::Utf8Value(env_->isolate, name).ToStringView()), | ||
| 218 | 219 | thread_count(thread_count_), | |
| 219 | - is_closing(false), | ||
| 220 | + state(kOpen), | ||
| 220 | 221 | dispatch_state(kDispatchIdle), | |
| 221 | 222 | context(context_), | |
| 222 | 223 | max_queue_size(max_queue_size_), | |
@@ -230,76 +231,104 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 230 | 231 | env->Ref(); | |
| 231 | 232 | } | |
| 232 | 233 | ||
| 233 | - ~ThreadSafeFunction() override { | ||
| 234 | - node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); | ||
| 235 | - env->Unref(); | ||
| 236 | - } | ||
| 234 | + ~ThreadSafeFunction() { ReleaseResources(); } | ||
| 237 | 235 | ||
| 238 | 236 | // These methods can be called from any thread. | |
| 239 | 237 | ||
| 240 | 238 | napi_status Push(void* data, napi_threadsafe_function_call_mode mode) { | |
| 241 | - node::Mutex::ScopedLock lock(this->mutex); | ||
| 239 | + { | ||
| 240 | + node::Mutex::ScopedLock lock(this->mutex); | ||
| 242 | 241 | ||
| 243 | - while (queue.size() >= max_queue_size && max_queue_size > 0 && | ||
| 244 | - !is_closing) { | ||
| 245 | - if (mode == napi_tsfn_nonblocking) { | ||
| 246 | - return napi_queue_full; | ||
| 242 | + while (queue.size() >= max_queue_size && max_queue_size > 0 && | ||
| 243 | + state == kOpen) { | ||
| 244 | + if (mode == napi_tsfn_nonblocking) { | ||
| 245 | + return napi_queue_full; | ||
| 246 | + } | ||
| 247 | + cond->Wait(lock); | ||
| 247 | 248 | } | |
| 248 | - cond->Wait(lock); | ||
| 249 | - } | ||
| 250 | 249 | ||
| 251 | - if (is_closing) { | ||
| 250 | + if (state == kOpen) { | ||
| 251 | + queue.push(data); | ||
| 252 | + Send(); | ||
| 253 | + return napi_ok; | ||
| 254 | + } | ||
| 252 | 255 | if (thread_count == 0) { | |
| 253 | 256 | return napi_invalid_arg; | |
| 254 | - } else { | ||
| 255 | - thread_count--; | ||
| 257 | + } | ||
| 258 | + thread_count--; | ||
| 259 | + if (!(state == kClosed && thread_count == 0)) { | ||
| 256 | 260 | return napi_closing; | |
| 257 | 261 | } | |
| 258 | - } else { | ||
| 259 | - queue.push(data); | ||
| 260 | - Send(); | ||
| 261 | - return napi_ok; | ||
| 262 | 262 | } | |
| 263 | + // Make sure to release lock before destroying | ||
| 264 | + delete this; | ||
| 265 | + return napi_closing; | ||
| 263 | 266 | } | |
| 264 | 267 | ||
| 265 | 268 | napi_status Acquire() { | |
| 266 | 269 | node::Mutex::ScopedLock lock(this->mutex); | |
| 267 | 270 | ||
| 268 | - if (is_closing) { | ||
| 269 | - return napi_closing; | ||
| 270 | - } | ||
| 271 | + if (state == kOpen) { | ||
| 272 | + thread_count++; | ||
| 271 | 273 | ||
| 272 | - thread_count++; | ||
| 274 | + return napi_ok; | ||
| 275 | + } | ||
| 273 | 276 | ||
| 274 | - return napi_ok; | ||
| 277 | + return napi_closing; | ||
| 275 | 278 | } | |
| 276 | 279 | ||
| 277 | 280 | napi_status Release(napi_threadsafe_function_release_mode mode) { | |
| 278 | - node::Mutex::ScopedLock lock(this->mutex); | ||
| 281 | + { | ||
| 282 | + node::Mutex::ScopedLock lock(this->mutex); | ||
| 279 | 283 | ||
| 280 | - if (thread_count == 0) { | ||
| 281 | - return napi_invalid_arg; | ||
| 282 | - } | ||
| 284 | + if (thread_count == 0) { | ||
| 285 | + return napi_invalid_arg; | ||
| 286 | + } | ||
| 283 | 287 | ||
| 284 | - thread_count--; | ||
| 288 | + thread_count--; | ||
| 285 | 289 | ||
| 286 | - if (thread_count == 0 || mode == napi_tsfn_abort) { | ||
| 287 | - if (!is_closing) { | ||
| 288 | - is_closing = (mode == napi_tsfn_abort); | ||
| 289 | - if (is_closing && max_queue_size > 0) { | ||
| 290 | - cond->Signal(lock); | ||
| 290 | + if (thread_count == 0 || mode == napi_tsfn_abort) { | ||
| 291 | + if (state == kOpen) { | ||
| 292 | + if (mode == napi_tsfn_abort) { | ||
| 293 | + state = kClosing; | ||
| 294 | + } | ||
| 295 | + if (state == kClosing && max_queue_size > 0) { | ||
| 296 | + cond->Signal(lock); | ||
| 297 | + } | ||
| 298 | + Send(); | ||
| 291 | 299 | } | |
| 292 | - Send(); | ||
| 293 | 300 | } | |
| 294 | - } | ||
| 295 | 301 | ||
| 302 | + if (!(state == kClosed && thread_count == 0)) { | ||
| 303 | + return napi_ok; | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | + // Make sure to release lock before destroying | ||
| 307 | + delete this; | ||
| 296 | 308 | return napi_ok; | |
| 297 | 309 | } | |
| 298 | 310 | ||
| 299 | - void EmptyQueueAndDelete() { | ||
| 300 | - for (; !queue.empty(); queue.pop()) { | ||
| 301 | - call_js_cb(nullptr, nullptr, context, queue.front()); | ||
| 311 | + void EmptyQueueAndMaybeDelete() { | ||
| 312 | + std::queue<void*> drain_queue; | ||
| 313 | + { | ||
| 314 | + node::Mutex::ScopedLock lock(this->mutex); | ||
| 315 | + queue.swap(drain_queue); | ||
| 302 | 316 | } | |
| 317 | + for (; !drain_queue.empty(); drain_queue.pop()) { | ||
| 318 | + call_js_cb(nullptr, nullptr, context, drain_queue.front()); | ||
| 319 | + } | ||
| 320 | + { | ||
| 321 | + node::Mutex::ScopedLock lock(this->mutex); | ||
| 322 | + if (thread_count > 0) { | ||
| 323 | + // At this point this TSFN is effectively done, but we need to keep | ||
| 324 | + // it alive for other threads that still have pointers to it until | ||
| 325 | + // they release them. | ||
| 326 | + // But we already release all the resources that we can at this point | ||
| 327 | + ReleaseResources(); | ||
| 328 | + return; | ||
| 329 | + } | ||
| 330 | + } | ||
| 331 | + // Make sure to release lock before destroying | ||
| 303 | 332 | delete this; | |
| 304 | 333 | } | |
| 305 | 334 | ||
@@ -351,6 +380,16 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 351 | 380 | inline void* Context() { return context; } | |
| 352 | 381 | ||
| 353 | 382 | protected: | |
| 383 | + void ReleaseResources() { | ||
| 384 | + if (state != kClosed) { | ||
| 385 | + state = kClosed; | ||
| 386 | + ref.Reset(); | ||
| 387 | + node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); | ||
| 388 | + env->Unref(); | ||
| 389 | + async_resource.reset(); | ||
| 390 | + } | ||
| 391 | + } | ||
| 392 | + | ||
| 354 | 393 | void Dispatch() { | |
| 355 | 394 | bool has_more = true; | |
| 356 | 395 | ||
@@ -379,9 +418,7 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 379 | 418 | ||
| 380 | 419 | { | |
| 381 | 420 | node::Mutex::ScopedLock lock(this->mutex); | |
| 382 | - if (is_closing) { | ||
| 383 | - CloseHandlesAndMaybeDelete(); | ||
| 384 | - } else { | ||
| 421 | + if (state == kOpen) { | ||
| 385 | 422 | size_t size = queue.size(); | |
| 386 | 423 | if (size > 0) { | |
| 387 | 424 | data = queue.front(); | |
@@ -395,7 +432,7 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 395 | 432 | ||
| 396 | 433 | if (size == 0) { | |
| 397 | 434 | if (thread_count == 0) { | |
| 398 | - is_closing = true; | ||
| 435 | + state = kClosing; | ||
| 399 | 436 | if (max_queue_size > 0) { | |
| 400 | 437 | cond->Signal(lock); | |
| 401 | 438 | } | |
@@ -404,12 +441,14 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 404 | 441 | } else { | |
| 405 | 442 | has_more = true; | |
| 406 | 443 | } | |
| 444 | + } else { | ||
| 445 | + CloseHandlesAndMaybeDelete(); | ||
| 407 | 446 | } | |
| 408 | 447 | } | |
| 409 | 448 | ||
| 410 | 449 | if (popped_value) { | |
| 411 | 450 | v8::HandleScope scope(env->isolate); | |
| 412 | - CallbackScope cb_scope(this); | ||
| 451 | + AsyncResource::CallbackScope cb_scope(&*async_resource); | ||
| 413 | 452 | napi_value js_callback = nullptr; | |
| 414 | 453 | if (!ref.IsEmpty()) { | |
| 415 | 454 | v8::Local<v8::Function> js_cb = | |
@@ -426,17 +465,17 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 426 | 465 | void Finalize() { | |
| 427 | 466 | v8::HandleScope scope(env->isolate); | |
| 428 | 467 | if (finalize_cb) { | |
| 429 | - CallbackScope cb_scope(this); | ||
| 468 | + AsyncResource::CallbackScope cb_scope(&*async_resource); | ||
| 430 | 469 | env->CallFinalizer<false>(finalize_cb, finalize_data, context); | |
| 431 | 470 | } | |
| 432 | - EmptyQueueAndDelete(); | ||
| 471 | + EmptyQueueAndMaybeDelete(); | ||
| 433 | 472 | } | |
| 434 | 473 | ||
| 435 | 474 | void CloseHandlesAndMaybeDelete(bool set_closing = false) { | |
| 436 | 475 | v8::HandleScope scope(env->isolate); | |
| 437 | 476 | if (set_closing) { | |
| 438 | 477 | node::Mutex::ScopedLock lock(this->mutex); | |
| 439 | - is_closing = true; | ||
| 478 | + state = kClosing; | ||
| 440 | 479 | if (max_queue_size > 0) { | |
| 441 | 480 | cond->Signal(lock); | |
| 442 | 481 | } | |
@@ -501,19 +540,30 @@ class ThreadSafeFunction : public node::AsyncResource { | |||
| 501 | 540 | } | |
| 502 | 541 | ||
| 503 | 542 | private: | |
| 543 | + // Needed because node::AsyncResource::CallbackScope is protected | ||
| 544 | + class AsyncResource : public node::AsyncResource { | ||
| 545 | + public: | ||
| 546 | + using node::AsyncResource::AsyncResource; | ||
| 547 | + using node::AsyncResource::CallbackScope; | ||
| 548 | + }; | ||
| 549 | + | ||
| 550 | + enum State : unsigned char { kOpen, kClosing, kClosed }; | ||
| 551 | + | ||
| 504 | 552 | static const unsigned char kDispatchIdle = 0; | |
| 505 | 553 | static const unsigned char kDispatchRunning = 1 << 0; | |
| 506 | 554 | static const unsigned char kDispatchPending = 1 << 1; | |
| 507 | 555 | ||
| 508 | 556 | static const unsigned int kMaxIterationCount = 1000; | |
| 509 | 557 | ||
| 558 | + std::optional<AsyncResource> async_resource; | ||
| 559 | + | ||
| 510 | 560 | // These are variables protected by the mutex. | |
| 511 | 561 | node::Mutex mutex; | |
| 512 | 562 | std::unique_ptr<node::ConditionVariable> cond; | |
| 513 | 563 | std::queue<void*> queue; | |
| 514 | 564 | uv_async_t async; | |
| 515 | 565 | size_t thread_count; | |
| 516 | - bool is_closing; | ||
| 566 | + State state; | ||
| 517 | 567 | std::atomic_uchar dispatch_state; | |
| 518 | 568 | ||
| 519 | 569 | // These are variables set once, upon creation, and then never again, which | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,83 @@ | |||
| 1 | + #include <js_native_api.h> | ||
| 2 | + #include <node_api.h> | ||
| 3 | + #include <node_api_types.h> | ||
| 4 | + | ||
| 5 | + #include <cstdio> | ||
| 6 | + #include <cstdlib> | ||
| 7 | + #include <memory> | ||
| 8 | + #include <thread> // NOLINT(build/c++11) | ||
| 9 | + #include <type_traits> | ||
| 10 | + #include <utility> | ||
| 11 | + | ||
| 12 | + template <typename R, auto func, typename... Args> | ||
| 13 | + inline auto call(const char* name, Args&&... args) -> R { | ||
| 14 | + napi_status status; | ||
| 15 | + if constexpr (std::is_same_v<R, void>) { | ||
| 16 | + status = func(std::forward<Args>(args)...); | ||
| 17 | + if (status == napi_ok) { | ||
| 18 | + return; | ||
| 19 | + } | ||
| 20 | + } else { | ||
| 21 | + R ret; | ||
| 22 | + status = func(std::forward<Args>(args)..., &ret); | ||
| 23 | + if (status == napi_ok) { | ||
| 24 | + return ret; | ||
| 25 | + } | ||
| 26 | + } | ||
| 27 | + std::fprintf(stderr, "%s: %d\n", name, status); | ||
| 28 | + std::abort(); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + #define NAPI_CALL(ret_type, func, ...) \ | ||
| 32 | + call<ret_type, func>(#func, ##__VA_ARGS__) | ||
| 33 | + | ||
| 34 | + void thread_func(napi_threadsafe_function tsfn) { | ||
| 35 | + fprintf(stderr, "thread_func: starting\n"); | ||
| 36 | + auto status = | ||
| 37 | + napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_blocking); | ||
| 38 | + while (status == napi_ok) { | ||
| 39 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 40 | + status = napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_blocking); | ||
| 41 | + } | ||
| 42 | + fprintf(stderr, "thread_func: Got status %d, exiting...\n", status); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + void tsfn_callback(napi_env env, napi_value js_cb, void* ctx, void* data) { | ||
| 46 | + if (env == nullptr) { | ||
| 47 | + fprintf(stderr, "tsfn_callback: env=%p\n", env); | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + void tsfn_finalize(napi_env env, void* finalize_data, void* finalize_hint) { | ||
| 52 | + fprintf(stderr, "tsfn_finalize: env=%p\n", env); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + auto run(napi_env env, napi_callback_info info) -> napi_value { | ||
| 56 | + auto global = NAPI_CALL(napi_value, napi_get_global, env); | ||
| 57 | + auto undefined = NAPI_CALL(napi_value, napi_get_undefined, env); | ||
| 58 | + auto n_threads = 32; | ||
| 59 | + auto tsfn = NAPI_CALL(napi_threadsafe_function, | ||
| 60 | + napi_create_threadsafe_function, | ||
| 61 | + env, | ||
| 62 | + nullptr, | ||
| 63 | + global, | ||
| 64 | + undefined, | ||
| 65 | + 0, | ||
| 66 | + n_threads, | ||
| 67 | + nullptr, | ||
| 68 | + tsfn_finalize, | ||
| 69 | + nullptr, | ||
| 70 | + tsfn_callback); | ||
| 71 | + for (auto i = 0; i < n_threads; ++i) { | ||
| 72 | + std::thread([tsfn] { thread_func(tsfn); }).detach(); | ||
| 73 | + } | ||
| 74 | + NAPI_CALL(void, napi_unref_threadsafe_function, env, tsfn); | ||
| 75 | + return NAPI_CALL(napi_value, napi_get_undefined, env); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + napi_value init(napi_env env, napi_value exports) { | ||
| 79 | + return NAPI_CALL( | ||
| 80 | + napi_value, napi_create_function, env, nullptr, 0, run, nullptr); | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + NAPI_MODULE(NODE_GYP_MODULE_NAME, init) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,11 @@ | |||
| 1 | + { | ||
| 2 | + "targets": [ | ||
| 3 | + { | ||
| 4 | + "target_name": "binding", | ||
| 5 | + "sources": ["binding.cc"], | ||
| 6 | + "cflags_cc": ["--std=c++20"], | ||
| 7 | + 'cflags!': [ '-fno-exceptions', '-fno-rtti' ], | ||
| 8 | + 'cflags_cc!': [ '-fno-exceptions', '-fno-rtti' ], | ||
| 9 | + } | ||
| 10 | + ] | ||
| 11 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,17 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../../common'); | ||
| 4 | + const process = require('process'); | ||
| 5 | + const assert = require('assert'); | ||
| 6 | + const { fork } = require('child_process'); | ||
| 7 | + const binding = require(`./build/${common.buildType}/binding`); | ||
| 8 | + | ||
| 9 | + if (process.argv[2] === 'child') { | ||
| 10 | + binding(); | ||
| 11 | + setTimeout(() => {}, 100); | ||
| 12 | + } else { | ||
| 13 | + const child = fork(__filename, ['child']); | ||
| 14 | + child.on('close', common.mustCall((code) => { | ||
| 15 | + assert.strictEqual(code, 0); | ||
| 16 | + })); | ||
| 17 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments