#include "async_wrap-inl.h" #include "env-inl.h" #define NAPI_EXPERIMENTAL #include "js_native_api_v8.h" #include "memory_tracker-inl.h" #include "node_api.h" #include "node_binding.h" #include "node_buffer.h" #include "node_errors.h" #include "node_internals.h" #include "threadpoolwork-inl.h" #include "tracing/traced_value.h" #include "util-inl.h" #include 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()); } bool can_call_into_js() const override { return node_env()->can_call_into_js(); } v8::Maybe mark_arraybuffer_as_untransferable( v8::Local ab) const override { return ab->SetPrivate( context(), node_env()->untransferable_object_private_symbol(), v8::True(isolate)); } void CallFinalizer(napi_finalize cb, void* data, void* hint) override { napi_env env = static_cast(this); node_env()->SetImmediate([=](node::Environment* node_env) { v8::HandleScope handle_scope(env->isolate); v8::Context::Scope context_scope(env->context()); env->CallIntoModule([&](napi_env env) { cb(env, data, hint); }); }); } }; 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) { std::unique_ptr finalizer{ static_cast(hint)}; finalizer->_finalize_data = data; node::Environment* node_env = static_cast(finalizer->_env)->node_env(); node_env->SetImmediate( [finalizer = std::move(finalizer)](node::Environment* env) { if (finalizer->_finalize_callback == nullptr) return; v8::HandleScope handle_scope(finalizer->_env->isolate); v8::Context::Scope context_scope(finalizer->_env->context()); finalizer->_env->CallIntoModule([&](napi_env env) { finalizer->_finalize_callback( env, finalizer->_finalize_data, finalizer->_finalize_hint); }); }); } struct Deleter { void operator()(BufferFinalizer* finalizer) { Finalizer::Delete(finalizer); } }; }; static inline napi_env NewEnv(v8::Local context) { node_napi_env result; result = new node_napi_env__(context); // 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 void trigger_fatal_exception( napi_env env, v8::Local local_err) { v8::Local local_msg = v8::Exception::CreateMessage(env->isolate, local_err); node::errors::TriggerUncaughtException(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() override { 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 = std::make_unique(); } if (max_queue_size == 0 || cond) { CHECK_EQ(0, uv_idle_init(loop, &idle)); 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; { 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 { CHECK_EQ(0, uv_idle_stop(&idle)); } } } } if (popped_value) { v8::HandleScope scope(env->isolate); CallbackScope cb_scope(this); napi_value js_callback = nullptr; if (!ref.IsEmpty()) { v8::Local js_cb = v8::Local::New(env->isolate, ref); js_callback = v8impl::JsValueFromV8LocalValue(js_cb); } env->CallIntoModule([&](napi_env env) { call_js_cb(env, js_callback, context, data); }); } } void Finalize() { v8::HandleScope scope(env->isolate); if (finalize_cb) { CallbackScope cb_scope(this); env->CallIntoModule([&](napi_env env) { 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); CHECK_EQ(0, uv_idle_start(&ts_fn->idle, IdleCb)); } 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; }; /** * Compared to node::AsyncResource, the resource object in AsyncContext is * gc-able. AsyncContext holds a weak reference to the resource object. * AsyncContext::MakeCallback doesn't implicitly set the receiver of the * callback to the resource object. */ class AsyncContext { public: AsyncContext(node_napi_env env, v8::Local resource_object, const v8::Local resource_name, bool externally_managed_resource) : env_(env) { async_id_ = node_env()->new_async_id(); trigger_async_id_ = node_env()->get_default_trigger_async_id(); resource_.Reset(node_env()->isolate(), resource_object); lost_reference_ = false; if (externally_managed_resource) { resource_.SetWeak( this, AsyncContext::WeakCallback, v8::WeakCallbackType::kParameter); } node::AsyncWrap::EmitAsyncInit(node_env(), resource_object, resource_name, async_id_, trigger_async_id_); } ~AsyncContext() { resource_.Reset(); lost_reference_ = true; node::AsyncWrap::EmitDestroy(node_env(), async_id_); } inline v8::MaybeLocal MakeCallback( v8::Local recv, const v8::Local callback, int argc, v8::Local argv[]) { EnsureReference(); return node::InternalMakeCallback(node_env(), resource(), recv, callback, argc, argv, {async_id_, trigger_async_id_}); } inline napi_callback_scope OpenCallbackScope() { EnsureReference(); napi_callback_scope it = reinterpret_cast(new CallbackScope(this)); env_->open_callback_scopes++; return it; } inline void EnsureReference() { if (lost_reference_) { const v8::HandleScope handle_scope(node_env()->isolate()); resource_.Reset(node_env()->isolate(), v8::Object::New(node_env()->isolate())); lost_reference_ = false; } } inline node::Environment* node_env() { return env_->node_env(); } inline v8::Local resource() { return resource_.Get(node_env()->isolate()); } inline node::async_context async_context() { return {async_id_, trigger_async_id_}; } static inline void CloseCallbackScope(node_napi_env env, napi_callback_scope s) { CallbackScope* callback_scope = reinterpret_cast(s); delete callback_scope; env->open_callback_scopes--; } static void WeakCallback(const v8::WeakCallbackInfo& data) { AsyncContext* async_context = data.GetParameter(); async_context->resource_.Reset(); async_context->lost_reference_ = true; } private: class CallbackScope : public node::CallbackScope { public: explicit CallbackScope(AsyncContext* async_context) : node::CallbackScope(async_context->node_env()->isolate(), async_context->resource_.Get( async_context->node_env()->isolate()), async_context->async_context()) {} }; node_napi_env env_; double async_id_; double trigger_async_id_; v8::Global resource_; bool lost_reference_; }; } // 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 specific module. napi_env env = v8impl::NewEnv(context); napi_value _exports; env->CallIntoModule([&](napi_env 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); } } namespace node { node_module napi_module_to_node_module(const napi_module* mod) { return { -1, mod->nm_flags | NM_F_DELETEME, nullptr, mod->nm_filename, nullptr, napi_module_register_cb, mod->nm_modname, const_cast(mod), // priv nullptr, }; } } // namespace node // Registers a NAPI module. void napi_module_register(napi_module* mod) { node::node_module* nm = new node::node_module( node::napi_module_to_node_module(mod)); 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; } struct napi_async_cleanup_hook_handle__ { napi_async_cleanup_hook_handle__(napi_env env, napi_async_cleanup_hook user_hook, void* user_data): env_(env), user_hook_(user_hook), user_data_(user_data) { handle_ = node::AddEnvironmentCleanupHook(env->isolate, Hook, this); env->Ref(); } ~napi_async_cleanup_hook_handle__() { node::RemoveEnvironmentCleanupHook(std::move(handle_)); if (done_cb_ != nullptr) done_cb_(done_data_); // Release the `env` handle asynchronously since it would be surprising if // a call to a N-API function would destroy `env` synchronously. static_cast(env_)->node_env() ->SetImmediate([env = env_](node::Environment*) { env->Unref(); }); } static void Hook(void* data, void (*done_cb)(void*), void* done_data) { auto handle = static_cast(data); handle->done_cb_ = done_cb; handle->done_data_ = done_data; handle->user_hook_(handle, handle->user_data_); } node::AsyncCleanupHookHandle handle_; napi_env env_ = nullptr; napi_async_cleanup_hook user_hook_ = nullptr; void* user_data_ = nullptr; void (*done_cb_)(void*) = nullptr; void* done_data_ = nullptr; }; napi_status napi_add_async_cleanup_hook( napi_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle) { CHECK_ENV(env); CHECK_ARG(env, hook); napi_async_cleanup_hook_handle__* handle = new napi_async_cleanup_hook_handle__(env, hook, arg); if (remove_handle != nullptr) *remove_handle = handle; return napi_clear_last_error(env); } napi_status napi_remove_async_cleanup_hook( napi_async_cleanup_hook_handle remove_handle) { if (remove_handle == nullptr) return napi_invalid_arg; delete remove_handle; 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 /** ignored */, 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); v8impl::AsyncContext* node_async_context = reinterpret_cast(async_context_handle); *result = node_async_context->OpenCallbackScope(); 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; } v8impl::AsyncContext::CloseCallbackScope(reinterpret_cast(env), 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; bool externally_managed_resource; if (async_resource != nullptr) { CHECK_TO_OBJECT(env, context, v8_resource, async_resource); externally_managed_resource = true; } else { v8_resource = v8::Object::New(isolate); externally_managed_resource = false; } v8::Local v8_resource_name; CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name); auto async_context = new v8impl::AsyncContext(reinterpret_cast(env), v8_resource, v8_resource_name, externally_managed_resource); *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); v8impl::AsyncContext* node_async_context = reinterpret_cast(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); v8::MaybeLocal callback_result; if (async_context == nullptr) { callback_result = node::MakeCallback( env->isolate, v8recv, v8func, argc, reinterpret_cast*>(const_cast(argv)), {0, 0}); } else { auto node_async_context = reinterpret_cast(async_context); callback_result = node_async_context->MakeCallback( v8recv, v8func, argc, reinterpret_cast*>(const_cast(argv))); } 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, v8impl::Finalizer::kKeepEnvReference); 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; default: 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) { } ~Work() override = default; 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); _env->CallIntoModule([&](napi_env env) { _complete(env, ConvertUVErrorCode(status), _data); }, [](napi_env 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); uv_loop_t* event_loop = nullptr; STATUS_CALL(napi_get_uv_event_loop(env, &event_loop)); 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, 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; if (func == nullptr) { CHECK_ARG(env, call_js_cb); } else { 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(); }