mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
Cope with the delay(to the next function call) of v8::Isolate::TerminateExecution() PR-URL: https://github.com/nodejs/node/pull/45620 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1356 lines
43 KiB
C++
1356 lines
43 KiB
C++
#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_api_internals.h"
|
|
#include "node_binding.h"
|
|
#include "node_buffer.h"
|
|
#include "node_errors.h"
|
|
#include "node_internals.h"
|
|
#include "node_process.h"
|
|
#include "node_url.h"
|
|
#include "threadpoolwork-inl.h"
|
|
#include "tracing/traced_value.h"
|
|
#include "util-inl.h"
|
|
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
|
|
const std::string& module_filename)
|
|
: napi_env__(context), filename(module_filename) {
|
|
CHECK_NOT_NULL(node_env());
|
|
}
|
|
|
|
void node_napi_env__::DeleteMe() {
|
|
destructing = true;
|
|
DrainFinalizerQueue();
|
|
napi_env__::DeleteMe();
|
|
}
|
|
|
|
bool node_napi_env__::can_call_into_js() const {
|
|
return node_env()->can_call_into_js();
|
|
}
|
|
|
|
v8::Maybe<bool> node_napi_env__::mark_arraybuffer_as_untransferable(
|
|
v8::Local<v8::ArrayBuffer> ab) const {
|
|
return ab->SetPrivate(context(),
|
|
node_env()->untransferable_object_private_symbol(),
|
|
v8::True(isolate));
|
|
}
|
|
|
|
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {
|
|
CallFinalizer<true>(cb, data, hint);
|
|
}
|
|
|
|
template <bool enforceUncaughtExceptionPolicy>
|
|
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Context::Scope context_scope(context());
|
|
CallbackIntoModule<enforceUncaughtExceptionPolicy>(
|
|
[&](napi_env env) { cb(env, data, hint); });
|
|
}
|
|
|
|
void node_napi_env__::EnqueueFinalizer(v8impl::RefTracker* finalizer) {
|
|
napi_env__::EnqueueFinalizer(finalizer);
|
|
// Schedule a second pass only when it has not been scheduled, and not
|
|
// destructing the env.
|
|
// When the env is being destructed, queued finalizers are drained in the
|
|
// loop of `node_napi_env__::DrainFinalizerQueue`.
|
|
if (!finalization_scheduled && !destructing) {
|
|
finalization_scheduled = true;
|
|
Ref();
|
|
node_env()->SetImmediate([this](node::Environment* node_env) {
|
|
finalization_scheduled = false;
|
|
Unref();
|
|
DrainFinalizerQueue();
|
|
});
|
|
}
|
|
}
|
|
|
|
void node_napi_env__::DrainFinalizerQueue() {
|
|
// As userland code can delete additional references in one finalizer,
|
|
// the list of pending finalizers may be mutated as we execute them, so
|
|
// we keep iterating it until it is empty.
|
|
while (!pending_finalizers.empty()) {
|
|
v8impl::RefTracker* ref_tracker = *pending_finalizers.begin();
|
|
pending_finalizers.erase(ref_tracker);
|
|
ref_tracker->Finalize();
|
|
}
|
|
}
|
|
|
|
void node_napi_env__::trigger_fatal_exception(v8::Local<v8::Value> local_err) {
|
|
v8::Local<v8::Message> local_msg =
|
|
v8::Exception::CreateMessage(isolate, local_err);
|
|
node::errors::TriggerUncaughtException(isolate, local_err, local_msg);
|
|
}
|
|
|
|
// option enforceUncaughtExceptionPolicy is added for not breaking existing
|
|
// running n-api add-ons, and should be deprecated in the next major Node.js
|
|
// release.
|
|
template <bool enforceUncaughtExceptionPolicy, typename T>
|
|
void node_napi_env__::CallbackIntoModule(T&& call) {
|
|
CallIntoModule(call, [](napi_env env_, v8::Local<v8::Value> local_err) {
|
|
node_napi_env__* env = static_cast<node_napi_env__*>(env_);
|
|
if (env->terminatedOrTerminating()) {
|
|
return;
|
|
}
|
|
node::Environment* node_env = env->node_env();
|
|
if (!node_env->options()->force_node_api_uncaught_exceptions_policy &&
|
|
!enforceUncaughtExceptionPolicy) {
|
|
ProcessEmitDeprecationWarning(
|
|
node_env,
|
|
"Uncaught N-API callback exception detected, please run node "
|
|
"with option --force-node-api-uncaught-exceptions-policy=true"
|
|
"to handle those exceptions properly.",
|
|
"DEP0168");
|
|
return;
|
|
}
|
|
// 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.)
|
|
env->trigger_fatal_exception(local_err);
|
|
});
|
|
}
|
|
|
|
namespace v8impl {
|
|
|
|
namespace {
|
|
|
|
class BufferFinalizer : private Finalizer {
|
|
public:
|
|
static BufferFinalizer* New(napi_env env,
|
|
napi_finalize finalize_callback = nullptr,
|
|
void* finalize_data = nullptr,
|
|
void* finalize_hint = nullptr) {
|
|
return new BufferFinalizer(
|
|
env, finalize_callback, finalize_data, finalize_hint);
|
|
}
|
|
// node::Buffer::FreeCallback
|
|
static void FinalizeBufferCallback(char* data, void* hint) {
|
|
std::unique_ptr<BufferFinalizer, Deleter> finalizer{
|
|
static_cast<BufferFinalizer*>(hint)};
|
|
finalizer->finalize_data_ = data;
|
|
|
|
// It is safe to call into JavaScript at this point.
|
|
if (finalizer->finalize_callback_ == nullptr) return;
|
|
finalizer->env_->CallFinalizer(finalizer->finalize_callback_,
|
|
finalizer->finalize_data_,
|
|
finalizer->finalize_hint_);
|
|
}
|
|
|
|
struct Deleter {
|
|
void operator()(BufferFinalizer* finalizer) { delete finalizer; }
|
|
};
|
|
|
|
private:
|
|
BufferFinalizer(napi_env env,
|
|
napi_finalize finalize_callback,
|
|
void* finalize_data,
|
|
void* finalize_hint)
|
|
: Finalizer(env, finalize_callback, finalize_data, finalize_hint) {
|
|
env_->Ref();
|
|
}
|
|
|
|
~BufferFinalizer() { env_->Unref(); }
|
|
};
|
|
|
|
inline napi_env NewEnv(v8::Local<v8::Context> context,
|
|
const std::string& module_filename) {
|
|
node_napi_env result;
|
|
|
|
result = new node_napi_env__(context, module_filename);
|
|
// 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<napi_env>(arg)->Unref(); },
|
|
static_cast<void*>(result));
|
|
|
|
return result;
|
|
}
|
|
|
|
class ThreadSafeFunction : public node::AsyncResource {
|
|
public:
|
|
ThreadSafeFunction(v8::Local<v8::Function> func,
|
|
v8::Local<v8::Object> resource,
|
|
v8::Local<v8::String> 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),
|
|
dispatch_state(kDispatchIdle),
|
|
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 {
|
|
queue.push(data);
|
|
Send();
|
|
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);
|
|
}
|
|
Send();
|
|
}
|
|
}
|
|
|
|
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<node::ConditionVariable>();
|
|
}
|
|
if (max_queue_size == 0 || cond) {
|
|
return napi_ok;
|
|
}
|
|
|
|
env->node_env()->CloseHandle(
|
|
reinterpret_cast<uv_handle_t*>(&async),
|
|
[](uv_handle_t* handle) -> void {
|
|
ThreadSafeFunction* ts_fn =
|
|
node::ContainerOf(&ThreadSafeFunction::async,
|
|
reinterpret_cast<uv_async_t*>(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<uv_handle_t*>(&async));
|
|
|
|
return napi_ok;
|
|
}
|
|
|
|
napi_status Ref() {
|
|
uv_ref(reinterpret_cast<uv_handle_t*>(&async));
|
|
|
|
return napi_ok;
|
|
}
|
|
|
|
inline void* Context() { return context; }
|
|
|
|
protected:
|
|
void Dispatch() {
|
|
bool has_more = true;
|
|
|
|
// Limit maximum synchronous iteration count to prevent event loop
|
|
// starvation. See `src/node_messaging.cc` for an inspiration.
|
|
unsigned int iterations_left = kMaxIterationCount;
|
|
while (has_more && --iterations_left != 0) {
|
|
dispatch_state = kDispatchRunning;
|
|
has_more = DispatchOne();
|
|
|
|
// Send() was called while we were executing the JS function
|
|
if (dispatch_state.exchange(kDispatchIdle) != kDispatchRunning) {
|
|
has_more = true;
|
|
}
|
|
}
|
|
|
|
if (has_more) {
|
|
Send();
|
|
}
|
|
}
|
|
|
|
bool DispatchOne() {
|
|
void* data = nullptr;
|
|
bool popped_value = false;
|
|
bool has_more = 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 {
|
|
has_more = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (popped_value) {
|
|
v8::HandleScope scope(env->isolate);
|
|
CallbackScope cb_scope(this);
|
|
napi_value js_callback = nullptr;
|
|
if (!ref.IsEmpty()) {
|
|
v8::Local<v8::Function> js_cb =
|
|
v8::Local<v8::Function>::New(env->isolate, ref);
|
|
js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
|
|
}
|
|
env->CallbackIntoModule<false>(
|
|
[&](napi_env env) { call_js_cb(env, js_callback, context, data); });
|
|
}
|
|
|
|
return has_more;
|
|
}
|
|
|
|
void Finalize() {
|
|
v8::HandleScope scope(env->isolate);
|
|
if (finalize_cb) {
|
|
CallbackScope cb_scope(this);
|
|
env->CallFinalizer<false>(finalize_cb, finalize_data, context);
|
|
}
|
|
EmptyQueueAndDelete();
|
|
}
|
|
|
|
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<uv_handle_t*>(&async),
|
|
[](uv_handle_t* handle) -> void {
|
|
ThreadSafeFunction* ts_fn =
|
|
node::ContainerOf(&ThreadSafeFunction::async,
|
|
reinterpret_cast<uv_async_t*>(handle));
|
|
ts_fn->Finalize();
|
|
});
|
|
}
|
|
|
|
void Send() {
|
|
// Ask currently running Dispatch() to make one more iteration
|
|
unsigned char current_state = dispatch_state.fetch_or(kDispatchPending);
|
|
if ((current_state & kDispatchRunning) == kDispatchRunning) {
|
|
return;
|
|
}
|
|
|
|
CHECK_EQ(0, uv_async_send(&async));
|
|
}
|
|
|
|
// 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 AsyncCb(uv_async_t* async) {
|
|
ThreadSafeFunction* ts_fn =
|
|
node::ContainerOf(&ThreadSafeFunction::async, async);
|
|
ts_fn->Dispatch();
|
|
}
|
|
|
|
static void Cleanup(void* data) {
|
|
reinterpret_cast<ThreadSafeFunction*>(data)->CloseHandlesAndMaybeDelete(
|
|
true);
|
|
}
|
|
|
|
private:
|
|
static const unsigned char kDispatchIdle = 0;
|
|
static const unsigned char kDispatchRunning = 1 << 0;
|
|
static const unsigned char kDispatchPending = 1 << 1;
|
|
|
|
static const unsigned int kMaxIterationCount = 1000;
|
|
|
|
// These are variables protected by the mutex.
|
|
node::Mutex mutex;
|
|
std::unique_ptr<node::ConditionVariable> cond;
|
|
std::queue<void*> queue;
|
|
uv_async_t async;
|
|
size_t thread_count;
|
|
bool is_closing;
|
|
std::atomic_uchar dispatch_state;
|
|
|
|
// 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<v8::Function> 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<v8::Object> resource_object,
|
|
const v8::Local<v8::String> 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<v8::Value> MakeCallback(
|
|
v8::Local<v8::Object> recv,
|
|
const v8::Local<v8::Function> callback,
|
|
int argc,
|
|
v8::Local<v8::Value> 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<napi_callback_scope>(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<v8::Object> 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<CallbackScope*>(s);
|
|
delete callback_scope;
|
|
env->open_callback_scopes--;
|
|
}
|
|
|
|
static void WeakCallback(const v8::WeakCallbackInfo<AsyncContext>& 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(),
|
|
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<v8::Object> 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<v8::Object> exports,
|
|
v8::Local<v8::Value> module,
|
|
v8::Local<v8::Context> context,
|
|
void* priv) {
|
|
napi_module_register_by_symbol(
|
|
exports,
|
|
module,
|
|
context,
|
|
static_cast<const napi_module*>(priv)->nm_register_func);
|
|
}
|
|
|
|
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
|
|
v8::Local<v8::Value> module,
|
|
v8::Local<v8::Context> context,
|
|
napi_addon_register_func init) {
|
|
node::Environment* node_env = node::Environment::GetCurrent(context);
|
|
std::string module_filename = "";
|
|
if (init == nullptr) {
|
|
CHECK_NOT_NULL(node_env);
|
|
node_env->ThrowError("Module has no declared entry point.");
|
|
return;
|
|
}
|
|
|
|
// We set `env->filename` from `module.filename` here, but we could just as
|
|
// easily add a private property to `exports` in `process.dlopen`, which
|
|
// receives the file name from JS, and retrieve *that* here. Thus, we are not
|
|
// endorsing commonjs here by making use of `module.filename`.
|
|
v8::Local<v8::Value> filename_js;
|
|
v8::Local<v8::Object> modobj;
|
|
if (module->ToObject(context).ToLocal(&modobj) &&
|
|
modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
|
|
filename_js->IsString()) {
|
|
node::Utf8Value filename(node_env->isolate(), filename_js);
|
|
|
|
// Turn the absolute path into a URL. Currently the absolute path is always
|
|
// a file system path.
|
|
// TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
|
|
// receive it as a URL already.
|
|
module_filename = node::url::URL::FromFilePath(filename.ToString()).href();
|
|
}
|
|
|
|
// Create a new napi_env for this specific module.
|
|
napi_env env = v8impl::NewEnv(context, module_filename);
|
|
|
|
napi_value _exports = nullptr;
|
|
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<napi_module*>(mod), // priv
|
|
nullptr,
|
|
};
|
|
}
|
|
} // namespace node
|
|
|
|
// Registers a NAPI module.
|
|
void NAPI_CDECL 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_CDECL napi_add_env_cleanup_hook(napi_env env,
|
|
napi_cleanup_hook fun,
|
|
void* arg) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, fun);
|
|
|
|
node::AddEnvironmentCleanupHook(env->isolate, fun, arg);
|
|
|
|
return napi_ok;
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_remove_env_cleanup_hook(napi_env env,
|
|
napi_cleanup_hook fun,
|
|
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<node_napi_env>(env_)->node_env()->SetImmediate(
|
|
[env = env_](node::Environment*) { env->Unref(); });
|
|
}
|
|
|
|
static void Hook(void* data, void (*done_cb)(void*), void* done_data) {
|
|
napi_async_cleanup_hook_handle__* handle =
|
|
static_cast<napi_async_cleanup_hook_handle__*>(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_CDECL
|
|
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_CDECL
|
|
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_CDECL napi_fatal_exception(napi_env env, napi_value err) {
|
|
NAPI_PREAMBLE(env);
|
|
CHECK_ARG(env, err);
|
|
|
|
v8::Local<v8::Value> local_err = v8impl::V8LocalValueFromJsValue(err);
|
|
static_cast<node_napi_env>(env)->trigger_fatal_exception(local_err);
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
NAPI_NO_RETURN void NAPI_CDECL 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<char*>(location), location_len);
|
|
} else {
|
|
location_string.assign(const_cast<char*>(location), strlen(location));
|
|
}
|
|
|
|
if (message_len != NAPI_AUTO_LENGTH) {
|
|
message_string.assign(const_cast<char*>(message), message_len);
|
|
} else {
|
|
message_string.assign(const_cast<char*>(message), strlen(message));
|
|
}
|
|
|
|
node::FatalError(location_string.c_str(), message_string.c_str());
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
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<v8impl::AsyncContext*>(async_context_handle);
|
|
|
|
*result = node_async_context->OpenCallbackScope();
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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<node_napi_env>(env),
|
|
scope);
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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<v8::Context> context = env->context();
|
|
|
|
v8::Local<v8::Object> 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::String> v8_resource_name;
|
|
CHECK_TO_STRING(env, context, v8_resource_name, async_resource_name);
|
|
|
|
v8impl::AsyncContext* async_context =
|
|
new v8impl::AsyncContext(reinterpret_cast<node_napi_env>(env),
|
|
v8_resource,
|
|
v8_resource_name,
|
|
externally_managed_resource);
|
|
|
|
*result = reinterpret_cast<napi_async_context>(async_context);
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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<v8impl::AsyncContext*>(async_context);
|
|
|
|
delete node_async_context;
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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<v8::Context> context = env->context();
|
|
|
|
v8::Local<v8::Object> v8recv;
|
|
CHECK_TO_OBJECT(env, context, v8recv, recv);
|
|
|
|
v8::Local<v8::Function> v8func;
|
|
CHECK_TO_FUNCTION(env, v8func, func);
|
|
|
|
v8::MaybeLocal<v8::Value> callback_result;
|
|
|
|
if (async_context == nullptr) {
|
|
callback_result = node::MakeCallback(
|
|
env->isolate,
|
|
v8recv,
|
|
v8func,
|
|
argc,
|
|
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
|
|
{0, 0});
|
|
} else {
|
|
v8impl::AsyncContext* node_async_context =
|
|
reinterpret_cast<v8impl::AsyncContext*>(async_context);
|
|
callback_result = node_async_context->MakeCallback(
|
|
v8recv,
|
|
v8func,
|
|
argc,
|
|
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(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_CDECL napi_create_buffer(napi_env env,
|
|
size_t length,
|
|
void** data,
|
|
napi_value* result) {
|
|
NAPI_PREAMBLE(env);
|
|
CHECK_ARG(env, result);
|
|
|
|
v8::MaybeLocal<v8::Object> maybe = node::Buffer::New(env->isolate, length);
|
|
|
|
CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
|
|
|
|
v8::Local<v8::Object> buffer = maybe.ToLocalChecked();
|
|
|
|
*result = v8impl::JsValueFromV8LocalValue(buffer);
|
|
|
|
if (data != nullptr) {
|
|
*data = node::Buffer::Data(buffer);
|
|
}
|
|
|
|
return GET_RETURN_STATUS(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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);
|
|
|
|
#if defined(V8_ENABLE_SANDBOX)
|
|
return napi_set_last_error(env, napi_no_external_buffers_allowed);
|
|
#endif
|
|
|
|
v8::Isolate* isolate = env->isolate;
|
|
|
|
// The finalizer object will delete itself after invoking the callback.
|
|
v8impl::BufferFinalizer* finalizer =
|
|
v8impl::BufferFinalizer::New(env, finalize_cb, nullptr, finalize_hint);
|
|
|
|
v8::MaybeLocal<v8::Object> maybe =
|
|
node::Buffer::New(isolate,
|
|
static_cast<char*>(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_CDECL 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);
|
|
|
|
v8::MaybeLocal<v8::Object> maybe =
|
|
node::Buffer::Copy(env->isolate, static_cast<const char*>(data), length);
|
|
|
|
CHECK_MAYBE_EMPTY(env, maybe, napi_generic_failure);
|
|
|
|
v8::Local<v8::Object> 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_CDECL 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_CDECL napi_get_buffer_info(napi_env env,
|
|
napi_value value,
|
|
void** data,
|
|
size_t* length) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, value);
|
|
|
|
v8::Local<v8::Value> 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_CDECL 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<v8::Object> async_resource,
|
|
v8::Local<v8::String> 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(), "node_api"),
|
|
_env(env),
|
|
_data(data),
|
|
_execute(execute),
|
|
_complete(complete) {}
|
|
|
|
~Work() override = default;
|
|
|
|
public:
|
|
static Work* New(node_napi_env env,
|
|
v8::Local<v8::Object> async_resource,
|
|
v8::Local<v8::String> 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->CallbackIntoModule<true>([&](napi_env env) {
|
|
_complete(env, ConvertUVErrorCode(status), _data);
|
|
});
|
|
|
|
// 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_CDECL
|
|
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<v8::Context> context = env->context();
|
|
|
|
v8::Local<v8::Object> resource;
|
|
if (async_resource != nullptr) {
|
|
CHECK_TO_OBJECT(env, context, resource, async_resource);
|
|
} else {
|
|
resource = v8::Object::New(env->isolate);
|
|
}
|
|
|
|
v8::Local<v8::String> resource_name;
|
|
CHECK_TO_STRING(env, context, resource_name, async_resource_name);
|
|
|
|
uvimpl::Work* work = uvimpl::Work::New(reinterpret_cast<node_napi_env>(env),
|
|
resource,
|
|
resource_name,
|
|
execute,
|
|
complete,
|
|
data);
|
|
|
|
*result = reinterpret_cast<napi_async_work>(work);
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_delete_async_work(napi_env env,
|
|
napi_async_work work) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, work);
|
|
|
|
uvimpl::Work::Delete(reinterpret_cast<uvimpl::Work*>(work));
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, loop);
|
|
*loop = reinterpret_cast<node_napi_env>(env)->node_env()->event_loop();
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL 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<uvimpl::Work*>(work);
|
|
|
|
w->ScheduleWork();
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_cancel_async_work(napi_env env,
|
|
napi_async_work work) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, work);
|
|
|
|
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
|
|
|
|
CALL_UV(env, w->CancelWork());
|
|
|
|
return napi_clear_last_error(env);
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
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::Function> v8_func;
|
|
if (func == nullptr) {
|
|
CHECK_ARG(env, call_js_cb);
|
|
} else {
|
|
CHECK_TO_FUNCTION(env, v8_func, func);
|
|
}
|
|
|
|
v8::Local<v8::Context> v8_context = env->context();
|
|
|
|
v8::Local<v8::Object> 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::String> 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<node_napi_env>(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<napi_threadsafe_function>(ts_fn);
|
|
}
|
|
}
|
|
|
|
return napi_set_last_error(env, status);
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_get_threadsafe_function_context(
|
|
napi_threadsafe_function func, void** result) {
|
|
CHECK_NOT_NULL(func);
|
|
CHECK_NOT_NULL(result);
|
|
|
|
*result = reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Context();
|
|
return napi_ok;
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
napi_call_threadsafe_function(napi_threadsafe_function func,
|
|
void* data,
|
|
napi_threadsafe_function_call_mode is_blocking) {
|
|
CHECK_NOT_NULL(func);
|
|
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Push(data,
|
|
is_blocking);
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
napi_acquire_threadsafe_function(napi_threadsafe_function func) {
|
|
CHECK_NOT_NULL(func);
|
|
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Acquire();
|
|
}
|
|
|
|
napi_status NAPI_CDECL napi_release_threadsafe_function(
|
|
napi_threadsafe_function func, napi_threadsafe_function_release_mode mode) {
|
|
CHECK_NOT_NULL(func);
|
|
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode);
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
|
|
CHECK_NOT_NULL(func);
|
|
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref();
|
|
}
|
|
|
|
napi_status NAPI_CDECL
|
|
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
|
|
CHECK_NOT_NULL(func);
|
|
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
|
|
}
|
|
|
|
napi_status NAPI_CDECL node_api_get_module_file_name(napi_env env,
|
|
const char** result) {
|
|
CHECK_ENV(env);
|
|
CHECK_ARG(env, result);
|
|
|
|
*result = static_cast<node_napi_env>(env)->GetFilename();
|
|
return napi_clear_last_error(env);
|
|
}
|