#include #include "env.h" #define NAPI_EXPERIMENTAL #include "js_native_api_v8.h" #include "node_api.h" #include "node_binding.h" #include "node_errors.h" #include "node_internals.h" struct node_napi_env__ : public napi_env__ { explicit node_napi_env__(v8::Local context): napi_env__(context) { CHECK_NOT_NULL(node_env()); } inline node::Environment* node_env() const { return node::Environment::GetCurrent(context()); } }; typedef node_napi_env__* node_napi_env; namespace v8impl { namespace { class BufferFinalizer: private Finalizer { public: // node::Buffer::FreeCallback static void FinalizeBufferCallback(char* data, void* hint) { BufferFinalizer* finalizer = static_cast(hint); if (finalizer->_finalize_callback != nullptr) { NAPI_CALL_INTO_MODULE_THROW(finalizer->_env, finalizer->_finalize_callback( finalizer->_env, data, finalizer->_finalize_hint)); } Delete(finalizer); } }; static inline napi_env GetEnv(v8::Local context) { node_napi_env result; auto isolate = context->GetIsolate(); auto global = context->Global(); // In the case of the string for which we grab the private and the value of // the private on the global object we can call .ToLocalChecked() directly // because we need to stop hard if either of them is empty. // // Re https://github.com/nodejs/node/pull/14217#discussion_r128775149 auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env)) .ToLocalChecked(); if (value->IsExternal()) { result = static_cast(value.As()->Value()); } else { result = new node_napi_env__(context); auto external = v8::External::New(isolate, result); // We must also stop hard if the result of assigning the env to the global // is either nothing or false. CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external) .FromJust()); // TODO(addaleax): There was previously code that tried to delete the // napi_env when its v8::Context was garbage collected; // However, as long as N-API addons using this napi_env are in place, // the Context needs to be accessible and alive. // Ideally, we'd want an on-addon-unload hook that takes care of this // once all N-API addons using this napi_env are unloaded. // For now, a per-Environment cleanup hook is the best we can do. result->node_env()->AddCleanupHook( [](void* arg) { static_cast(arg)->Unref(); }, static_cast(result)); } return result; } static inline napi_callback_scope JsCallbackScopeFromV8CallbackScope(node::CallbackScope* s) { return reinterpret_cast(s); } static inline node::CallbackScope* V8CallbackScopeFromJsCallbackScope(napi_callback_scope s) { return reinterpret_cast(s); } static inline void trigger_fatal_exception( napi_env env, v8::Local local_err) { v8::Local local_msg = v8::Exception::CreateMessage(env->isolate, local_err); node::FatalException(env->isolate, local_err, local_msg); } class ThreadSafeFunction : public node::AsyncResource { public: ThreadSafeFunction(v8::Local func, v8::Local resource, v8::Local name, size_t thread_count_, void* context_, size_t max_queue_size_, node_napi_env env_, void* finalize_data_, napi_finalize finalize_cb_, napi_threadsafe_function_call_js call_js_cb_): AsyncResource(env_->isolate, resource, *v8::String::Utf8Value(env_->isolate, name)), thread_count(thread_count_), is_closing(false), context(context_), max_queue_size(max_queue_size_), env(env_), finalize_data(finalize_data_), finalize_cb(finalize_cb_), call_js_cb(call_js_cb_ == nullptr ? CallJs : call_js_cb_), handles_closing(false) { ref.Reset(env->isolate, func); node::AddEnvironmentCleanupHook(env->isolate, Cleanup, this); env->Ref(); } ~ThreadSafeFunction() { node::RemoveEnvironmentCleanupHook(env->isolate, Cleanup, this); env->Unref(); } // These methods can be called from any thread. napi_status Push(void* data, napi_threadsafe_function_call_mode mode) { node::Mutex::ScopedLock lock(this->mutex); while (queue.size() >= max_queue_size && max_queue_size > 0 && !is_closing) { if (mode == napi_tsfn_nonblocking) { return napi_queue_full; } cond->Wait(lock); } if (is_closing) { if (thread_count == 0) { return napi_invalid_arg; } else { thread_count--; return napi_closing; } } else { if (uv_async_send(&async) != 0) { return napi_generic_failure; } queue.push(data); return napi_ok; } } napi_status Acquire() { node::Mutex::ScopedLock lock(this->mutex); if (is_closing) { return napi_closing; } thread_count++; return napi_ok; } napi_status Release(napi_threadsafe_function_release_mode mode) { node::Mutex::ScopedLock lock(this->mutex); if (thread_count == 0) { return napi_invalid_arg; } thread_count--; if (thread_count == 0 || mode == napi_tsfn_abort) { if (!is_closing) { is_closing = (mode == napi_tsfn_abort); if (is_closing && max_queue_size > 0) { cond->Signal(lock); } if (uv_async_send(&async) != 0) { return napi_generic_failure; } } } return napi_ok; } void EmptyQueueAndDelete() { for (; !queue.empty() ; queue.pop()) { call_js_cb(nullptr, nullptr, context, queue.front()); } delete this; } // These methods must only be called from the loop thread. napi_status Init() { ThreadSafeFunction* ts_fn = this; uv_loop_t* loop = env->node_env()->event_loop(); if (uv_async_init(loop, &async, AsyncCb) == 0) { if (max_queue_size > 0) { cond.reset(new node::ConditionVariable); } if ((max_queue_size == 0 || cond.get() != nullptr) && uv_idle_init(loop, &idle) == 0) { return napi_ok; } env->node_env()->CloseHandle( reinterpret_cast(&async), [](uv_handle_t* handle) -> void { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::async, reinterpret_cast(handle)); delete ts_fn; }); // Prevent the thread-safe function from being deleted here, because // the callback above will delete it. ts_fn = nullptr; } delete ts_fn; return napi_generic_failure; } napi_status Unref() { uv_unref(reinterpret_cast(&async)); uv_unref(reinterpret_cast(&idle)); return napi_ok; } napi_status Ref() { uv_ref(reinterpret_cast(&async)); uv_ref(reinterpret_cast(&idle)); return napi_ok; } void DispatchOne() { void* data = nullptr; bool popped_value = false; bool idle_stop_failed = false; { node::Mutex::ScopedLock lock(this->mutex); if (is_closing) { CloseHandlesAndMaybeDelete(); } else { size_t size = queue.size(); if (size > 0) { data = queue.front(); queue.pop(); popped_value = true; if (size == max_queue_size && max_queue_size > 0) { cond->Signal(lock); } size--; } if (size == 0) { if (thread_count == 0) { is_closing = true; if (max_queue_size > 0) { cond->Signal(lock); } CloseHandlesAndMaybeDelete(); } else { if (uv_idle_stop(&idle) != 0) { idle_stop_failed = true; } } } } } if (popped_value || idle_stop_failed) { v8::HandleScope scope(env->isolate); CallbackScope cb_scope(this); if (idle_stop_failed) { CHECK(napi_throw_error(env, "ERR_NAPI_TSFN_STOP_IDLE_LOOP", "Failed to stop the idle loop") == napi_ok); } else { v8::Local js_cb = v8::Local::New(env->isolate, ref); call_js_cb(env, v8impl::JsValueFromV8LocalValue(js_cb), context, data); } } } void MaybeStartIdle() { if (uv_idle_start(&idle, IdleCb) != 0) { v8::HandleScope scope(env->isolate); CallbackScope cb_scope(this); CHECK(napi_throw_error(env, "ERR_NAPI_TSFN_START_IDLE_LOOP", "Failed to start the idle loop") == napi_ok); } } void Finalize() { v8::HandleScope scope(env->isolate); if (finalize_cb) { CallbackScope cb_scope(this); finalize_cb(env, finalize_data, context); } EmptyQueueAndDelete(); } inline void* Context() { return context; } void CloseHandlesAndMaybeDelete(bool set_closing = false) { v8::HandleScope scope(env->isolate); if (set_closing) { node::Mutex::ScopedLock lock(this->mutex); is_closing = true; if (max_queue_size > 0) { cond->Signal(lock); } } if (handles_closing) { return; } handles_closing = true; env->node_env()->CloseHandle( reinterpret_cast(&async), [](uv_handle_t* handle) -> void { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::async, reinterpret_cast(handle)); v8::HandleScope scope(ts_fn->env->isolate); ts_fn->env->node_env()->CloseHandle( reinterpret_cast(&ts_fn->idle), [](uv_handle_t* handle) -> void { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::idle, reinterpret_cast(handle)); ts_fn->Finalize(); }); }); } // Default way of calling into JavaScript. Used when ThreadSafeFunction is // without a call_js_cb_. static void CallJs(napi_env env, napi_value cb, void* context, void* data) { if (!(env == nullptr || cb == nullptr)) { napi_value recv; napi_status status; status = napi_get_undefined(env, &recv); if (status != napi_ok) { napi_throw_error(env, "ERR_NAPI_TSFN_GET_UNDEFINED", "Failed to retrieve undefined value"); return; } status = napi_call_function(env, recv, cb, 0, nullptr, nullptr); if (status != napi_ok && status != napi_pending_exception) { napi_throw_error(env, "ERR_NAPI_TSFN_CALL_JS", "Failed to call JS callback"); return; } } } static void IdleCb(uv_idle_t* idle) { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::idle, idle); ts_fn->DispatchOne(); } static void AsyncCb(uv_async_t* async) { ThreadSafeFunction* ts_fn = node::ContainerOf(&ThreadSafeFunction::async, async); ts_fn->MaybeStartIdle(); } static void Cleanup(void* data) { reinterpret_cast(data) ->CloseHandlesAndMaybeDelete(true); } private: // These are variables protected by the mutex. node::Mutex mutex; std::unique_ptr cond; std::queue queue; uv_async_t async; uv_idle_t idle; size_t thread_count; bool is_closing; // These are variables set once, upon creation, and then never again, which // means we don't need the mutex to read them. void* context; size_t max_queue_size; // These are variables accessed only from the loop thread. v8impl::Persistent ref; node_napi_env env; void* finalize_data; napi_finalize finalize_cb; napi_threadsafe_function_call_js call_js_cb; bool handles_closing; }; } // end of anonymous namespace } // end of namespace v8impl // Intercepts the Node-V8 module registration callback. Converts parameters // to NAPI equivalents and then calls the registration callback specified // by the NAPI module. static void napi_module_register_cb(v8::Local exports, v8::Local module, v8::Local context, void* priv) { napi_module_register_by_symbol(exports, module, context, static_cast(priv)->nm_register_func); } void napi_module_register_by_symbol(v8::Local exports, v8::Local module, v8::Local context, napi_addon_register_func init) { if (init == nullptr) { node::Environment* node_env = node::Environment::GetCurrent(context); CHECK_NOT_NULL(node_env); node_env->ThrowError( "Module has no declared entry point."); return; } // Create a new napi_env for this module or reference one if a pre-existing // one is found. napi_env env = v8impl::GetEnv(context); napi_value _exports; NAPI_CALL_INTO_MODULE_THROW(env, _exports = init(env, v8impl::JsValueFromV8LocalValue(exports))); // If register function returned a non-null exports object different from // the exports object we passed it, set that as the "exports" property of // the module. if (_exports != nullptr && _exports != v8impl::JsValueFromV8LocalValue(exports)) { napi_value _module = v8impl::JsValueFromV8LocalValue(module); napi_set_named_property(env, _module, "exports", _exports); } } // Registers a NAPI module. void napi_module_register(napi_module* mod) { node::node_module* nm = new node::node_module { -1, mod->nm_flags, nullptr, mod->nm_filename, nullptr, napi_module_register_cb, mod->nm_modname, mod, // priv nullptr, }; node::node_module_register(nm); } napi_status napi_add_env_cleanup_hook(napi_env env, void (*fun)(void* arg), void* arg) { CHECK_ENV(env); CHECK_ARG(env, fun); node::AddEnvironmentCleanupHook(env->isolate, fun, arg); return napi_ok; } napi_status napi_remove_env_cleanup_hook(napi_env env, void (*fun)(void* arg), void* arg) { CHECK_ENV(env); CHECK_ARG(env, fun); node::RemoveEnvironmentCleanupHook(env->isolate, fun, arg); return napi_ok; } napi_status napi_fatal_exception(napi_env env, napi_value err) { NAPI_PREAMBLE(env); CHECK_ARG(env, err); v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); v8impl::trigger_fatal_exception(env, local_err); return napi_clear_last_error(env); } NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, size_t message_len) { std::string location_string; std::string message_string; if (location_len != NAPI_AUTO_LENGTH) { location_string.assign( const_cast(location), location_len); } else { location_string.assign( const_cast(location), strlen(location)); } if (message_len != NAPI_AUTO_LENGTH) { message_string.assign( const_cast(message), message_len); } else { message_string.assign( const_cast(message), strlen(message)); } node::FatalError(location_string.c_str(), message_string.c_str()); } napi_status napi_open_callback_scope(napi_env env, napi_value resource_object, napi_async_context async_context_handle, napi_callback_scope* result) { // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // JS exceptions. CHECK_ENV(env); CHECK_ARG(env, result); v8::Local context = env->context(); node::async_context* node_async_context = reinterpret_cast(async_context_handle); v8::Local resource; CHECK_TO_OBJECT(env, context, resource, resource_object); *result = v8impl::JsCallbackScopeFromV8CallbackScope( new node::CallbackScope(env->isolate, resource, *node_async_context)); env->open_callback_scopes++; return napi_clear_last_error(env); } napi_status napi_close_callback_scope(napi_env env, napi_callback_scope scope) { // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // JS exceptions. CHECK_ENV(env); CHECK_ARG(env, scope); if (env->open_callback_scopes == 0) { return napi_callback_scope_mismatch; } env->open_callback_scopes--; delete v8impl::V8CallbackScopeFromJsCallbackScope(scope); return napi_clear_last_error(env); } napi_status napi_async_init(napi_env env, napi_value async_resource, napi_value async_resource_name, napi_async_context* result) { CHECK_ENV(env); CHECK_ARG(env, async_resource_name); CHECK_ARG(env, result); v8::Isolate* isolate = env->isolate; v8::Local context = env->context(); v8::Local v8_resource; if (async_resource != nullptr) { CHECK_TO_OBJECT(env, context, v8_resource, async_resource); } else { v8_resource = v8::Object::New(isolate); } v8::Local v8_resource_name; CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name); // TODO(jasongin): Consider avoiding allocation here by using // a tagged pointer with 2×31 bit fields instead. node::async_context* async_context = new node::async_context(); *async_context = node::EmitAsyncInit(isolate, v8_resource, v8_resource_name); *result = reinterpret_cast(async_context); return napi_clear_last_error(env); } napi_status napi_async_destroy(napi_env env, napi_async_context async_context) { CHECK_ENV(env); CHECK_ARG(env, async_context); v8::Isolate* isolate = env->isolate; node::async_context* node_async_context = reinterpret_cast(async_context); node::EmitAsyncDestroy(isolate, *node_async_context); delete node_async_context; return napi_clear_last_error(env); } napi_status napi_make_callback(napi_env env, napi_async_context async_context, napi_value recv, napi_value func, size_t argc, const napi_value* argv, napi_value* result) { NAPI_PREAMBLE(env); CHECK_ARG(env, recv); if (argc > 0) { CHECK_ARG(env, argv); } v8::Local context = env->context(); v8::Local v8recv; CHECK_TO_OBJECT(env, context, v8recv, recv); v8::Local v8func; CHECK_TO_FUNCTION(env, v8func, func); node::async_context* node_async_context = reinterpret_cast(async_context); if (node_async_context == nullptr) { static node::async_context empty_context = { 0, 0 }; node_async_context = &empty_context; } v8::MaybeLocal callback_result = node::MakeCallback( env->isolate, v8recv, v8func, argc, reinterpret_cast*>(const_cast(argv)), *node_async_context); if (try_catch.HasCaught()) { return napi_set_last_error(env, napi_pending_exception); } else { CHECK_MAYBE_EMPTY(env, callback_result, napi_generic_failure); if (result != nullptr) { *result = v8impl::JsValueFromV8LocalValue( callback_result.ToLocalChecked()); } } return GET_RETURN_STATUS(env); } napi_status napi_create_buffer(napi_env env, size_t length, void** data, napi_value* result) { NAPI_PREAMBLE(env); CHECK_ARG(env, result); auto maybe = node::Buffer::New(env->isolate, length); CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); v8::Local buffer = maybe.ToLocalChecked(); *result = v8impl::JsValueFromV8LocalValue(buffer); if (data != nullptr) { *data = node::Buffer::Data(buffer); } return GET_RETURN_STATUS(env); } napi_status napi_create_external_buffer(napi_env env, size_t length, void* data, napi_finalize finalize_cb, void* finalize_hint, napi_value* result) { NAPI_PREAMBLE(env); CHECK_ARG(env, result); v8::Isolate* isolate = env->isolate; // The finalizer object will delete itself after invoking the callback. v8impl::Finalizer* finalizer = v8impl::Finalizer::New( env, finalize_cb, nullptr, finalize_hint); auto maybe = node::Buffer::New(isolate, static_cast(data), length, v8impl::BufferFinalizer::FinalizeBufferCallback, finalizer); CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); *result = v8impl::JsValueFromV8LocalValue(maybe.ToLocalChecked()); return GET_RETURN_STATUS(env); // Tell coverity that 'finalizer' should not be freed when we return // as it will be deleted when the buffer to which it is associated // is finalized. // coverity[leaked_storage] } napi_status napi_create_buffer_copy(napi_env env, size_t length, const void* data, void** result_data, napi_value* result) { NAPI_PREAMBLE(env); CHECK_ARG(env, result); auto maybe = node::Buffer::Copy(env->isolate, static_cast(data), length); CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure); v8::Local buffer = maybe.ToLocalChecked(); *result = v8impl::JsValueFromV8LocalValue(buffer); if (result_data != nullptr) { *result_data = node::Buffer::Data(buffer); } return GET_RETURN_STATUS(env); } napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) { CHECK_ENV(env); CHECK_ARG(env, value); CHECK_ARG(env, result); *result = node::Buffer::HasInstance(v8impl::V8LocalValueFromJsValue(value)); return napi_clear_last_error(env); } napi_status napi_get_buffer_info(napi_env env, napi_value value, void** data, size_t* length) { CHECK_ENV(env); CHECK_ARG(env, value); v8::Local buffer = v8impl::V8LocalValueFromJsValue(value); if (data != nullptr) { *data = node::Buffer::Data(buffer); } if (length != nullptr) { *length = node::Buffer::Length(buffer); } return napi_clear_last_error(env); } napi_status napi_get_node_version(napi_env env, const napi_node_version** result) { CHECK_ENV(env); CHECK_ARG(env, result); static const napi_node_version version = { NODE_MAJOR_VERSION, NODE_MINOR_VERSION, NODE_PATCH_VERSION, NODE_RELEASE }; *result = &version; return napi_clear_last_error(env); } namespace { namespace uvimpl { static napi_status ConvertUVErrorCode(int code) { switch (code) { case 0: return napi_ok; case UV_EINVAL: return napi_invalid_arg; case UV_ECANCELED: return napi_cancelled; } return napi_generic_failure; } // Wrapper around uv_work_t which calls user-provided callbacks. class Work : public node::AsyncResource, public node::ThreadPoolWork { private: explicit Work(node_napi_env env, v8::Local async_resource, v8::Local async_resource_name, napi_async_execute_callback execute, napi_async_complete_callback complete = nullptr, void* data = nullptr) : AsyncResource(env->isolate, async_resource, *v8::String::Utf8Value(env->isolate, async_resource_name)), ThreadPoolWork(env->node_env()), _env(env), _data(data), _execute(execute), _complete(complete) { } virtual ~Work() { } public: static Work* New(node_napi_env env, v8::Local async_resource, v8::Local async_resource_name, napi_async_execute_callback execute, napi_async_complete_callback complete, void* data) { return new Work(env, async_resource, async_resource_name, execute, complete, data); } static void Delete(Work* work) { delete work; } void DoThreadPoolWork() override { _execute(_env, _data); } void AfterThreadPoolWork(int status) override { if (_complete == nullptr) return; // Establish a handle scope here so that every callback doesn't have to. // Also it is needed for the exception-handling below. v8::HandleScope scope(_env->isolate); CallbackScope callback_scope(this); // We have to back up the env here because the `NAPI_CALL_INTO_MODULE` macro // makes use of it after the call into the module completes, but the module // may have deallocated **this**, and along with it the place where _env is // stored. napi_env env = _env; NAPI_CALL_INTO_MODULE(env, _complete(_env, ConvertUVErrorCode(status), _data), [env] (v8::Local local_err) { // If there was an unhandled exception in the complete callback, // report it as a fatal exception. (There is no JavaScript on the // callstack that can possibly handle it.) v8impl::trigger_fatal_exception(env, local_err); }); // Note: Don't access `work` after this point because it was // likely deleted by the complete callback. } private: node_napi_env _env; void* _data; napi_async_execute_callback _execute; napi_async_complete_callback _complete; }; } // end of namespace uvimpl } // end of anonymous namespace #define CALL_UV(env, condition) \ do { \ int result = (condition); \ napi_status status = uvimpl::ConvertUVErrorCode(result); \ if (status != napi_ok) { \ return napi_set_last_error(env, status, result); \ } \ } while (0) napi_status napi_create_async_work(napi_env env, napi_value async_resource, napi_value async_resource_name, napi_async_execute_callback execute, napi_async_complete_callback complete, void* data, napi_async_work* result) { CHECK_ENV(env); CHECK_ARG(env, execute); CHECK_ARG(env, result); v8::Local context = env->context(); v8::Local resource; if (async_resource != nullptr) { CHECK_TO_OBJECT(env, context, resource, async_resource); } else { resource = v8::Object::New(env->isolate); } v8::Local resource_name; CHECK_TO_STRING(env, context, resource_name, async_resource_name); uvimpl::Work* work = uvimpl::Work::New(reinterpret_cast(env), resource, resource_name, execute, complete, data); *result = reinterpret_cast(work); return napi_clear_last_error(env); } napi_status napi_delete_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); uvimpl::Work::Delete(reinterpret_cast(work)); return napi_clear_last_error(env); } napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { CHECK_ENV(env); CHECK_ARG(env, loop); *loop = reinterpret_cast(env)->node_env()->event_loop(); return napi_clear_last_error(env); } napi_status napi_queue_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); napi_status status; uv_loop_t* event_loop = nullptr; status = napi_get_uv_event_loop(env, &event_loop); if (status != napi_ok) return napi_set_last_error(env, status); uvimpl::Work* w = reinterpret_cast(work); w->ScheduleWork(); return napi_clear_last_error(env); } napi_status napi_cancel_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); uvimpl::Work* w = reinterpret_cast(work); CALL_UV(env, w->CancelWork()); return napi_clear_last_error(env); } napi_status napi_create_threadsafe_function(napi_env env, napi_value func, napi_value async_resource, napi_value async_resource_name, size_t max_queue_size, size_t initial_thread_count, void* thread_finalize_data, napi_finalize thread_finalize_cb, void* context, napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function* result) { CHECK_ENV(env); CHECK_ARG(env, func); CHECK_ARG(env, async_resource_name); RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg); CHECK_ARG(env, result); napi_status status = napi_ok; v8::Local v8_func; CHECK_TO_FUNCTION(env, v8_func, func); v8::Local v8_context = env->context(); v8::Local v8_resource; if (async_resource == nullptr) { v8_resource = v8::Object::New(env->isolate); } else { CHECK_TO_OBJECT(env, v8_context, v8_resource, async_resource); } v8::Local v8_name; CHECK_TO_STRING(env, v8_context, v8_name, async_resource_name); v8impl::ThreadSafeFunction* ts_fn = new v8impl::ThreadSafeFunction(v8_func, v8_resource, v8_name, initial_thread_count, context, max_queue_size, reinterpret_cast(env), thread_finalize_data, thread_finalize_cb, call_js_cb); if (ts_fn == nullptr) { status = napi_generic_failure; } else { // Init deletes ts_fn upon failure. status = ts_fn->Init(); if (status == napi_ok) { *result = reinterpret_cast(ts_fn); } } return napi_set_last_error(env, status); } napi_status napi_get_threadsafe_function_context(napi_threadsafe_function func, void** result) { CHECK_NOT_NULL(func); CHECK_NOT_NULL(result); *result = reinterpret_cast(func)->Context(); return napi_ok; } napi_status napi_call_threadsafe_function(napi_threadsafe_function func, void* data, napi_threadsafe_function_call_mode is_blocking) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Push(data, is_blocking); } napi_status napi_acquire_threadsafe_function(napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Acquire(); } napi_status napi_release_threadsafe_function(napi_threadsafe_function func, napi_threadsafe_function_release_mode mode) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Release(mode); } napi_status napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Unref(); } napi_status napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Ref(); }