/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v26.0.0
src/node_locks.cc
883 строки
30 KB
James M Snell
src: make additional cleanups in node locks impl
11 окт 2025, 18:11
11 окт 2025, 18:11
a1244f0
Код
Авторство
О чём код?
#include "node_locks.h" #include "base_object-inl.h" #include "env-inl.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "util-inl.h" #include "v8.h" namespace node::worker::locks { using node::errors::TryCatchScope; using v8::Array; using v8::Context; using v8::DictionaryTemplate; using v8::Exception; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; using v8::Object; using v8::ObjectTemplate; using v8::Promise; using v8::PropertyAttribute; using v8::Value; // Reject two promises and return `false` on failure. static bool RejectBoth(Local<Context> ctx, Local<Promise::Resolver> first, Local<Promise::Resolver> second, Local<Value> reason) { return first->Reject(ctx, reason).IsJust() && second->Reject(ctx, reason).IsJust(); } Lock::Lock(Environment* env, const std::u16string& name, Mode mode, const std::string& client_id, Local<Promise::Resolver> waiting, Local<Promise::Resolver> released) : env_(env), name_(name), mode_(mode), client_id_(client_id) { waiting_promise_.Reset(env_->isolate(), waiting); released_promise_.Reset(env_->isolate(), released); } void Lock::MemoryInfo(node::MemoryTracker* tracker) const { tracker->TrackFieldWithSize("name", name_.size()); tracker->TrackField("client_id", client_id_); tracker->TrackField("waiting_promise", waiting_promise_); tracker->TrackField("released_promise", released_promise_); } LockRequest::LockRequest(Environment* env, Local<Promise::Resolver> waiting, Local<Promise::Resolver> released, Local<Function> callback, const std::u16string& name, Lock::Mode mode, std::string client_id, bool steal, bool if_available) : env_(env), name_(name), mode_(mode), client_id_(std::move(client_id)), steal_(steal), if_available_(if_available) { waiting_promise_.Reset(env_->isolate(), waiting); released_promise_.Reset(env_->isolate(), released); callback_.Reset(env_->isolate(), callback); } Local<DictionaryTemplate> GetLockInfoTemplate(Environment* env) { auto tmpl = env->lock_info_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "name", "mode", "clientId", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_lock_info_template(tmpl); } return tmpl; } // The request here can be either a Lock or a LockRequest. static MaybeLocal<Object> CreateLockInfoObject(Environment* env, const auto& request) { auto tmpl = GetLockInfoTemplate(env); MaybeLocal<Value> values[] = { ToV8Value(env->context(), request.name()), request.mode() == Lock::Mode::Exclusive ? env->exclusive_string() : env->shared_string(), ToV8Value(env->context(), request.client_id()), }; return NewDictionaryInstance(env->context(), tmpl, values); } bool LockManager::IsGrantable(const LockRequest* request) const { // Steal requests bypass all normal granting rules if (request->steal()) return true; auto held_locks_iter = held_locks_.find(request->name()); // No existing locks for this resource name if (held_locks_iter == held_locks_.end()) return true; // Exclusive requests cannot coexist with any existing locks if (request->mode() == Lock::Mode::Exclusive) return false; // For shared requests, check if any existing lock is exclusive for (const auto& existing_lock : held_locks_iter->second) { if (existing_lock->mode() == Lock::Mode::Exclusive) return false; } // All existing locks are shared, so this shared request can be granted return true; } // Called when the user callback promise fulfills static void OnLockCallbackFulfilled(const FunctionCallbackInfo<Value>& info) { HandleScope handle_scope(info.GetIsolate()); Environment* env = Environment::GetCurrent(info); BaseObjectPtr<LockHolder> lock_holder{ BaseObject::FromJSObject<LockHolder>(info.Data())}; std::shared_ptr<Lock> lock = lock_holder->lock(); // Release the lock and continue processing the queue. LockManager::GetCurrent()->ReleaseLockAndProcessQueue( env, lock, info[0], false); } // Called when the user callback promise rejects static void OnLockCallbackRejected(const FunctionCallbackInfo<Value>& info) { HandleScope handle_scope(info.GetIsolate()); Environment* env = Environment::GetCurrent(info); BaseObjectPtr<LockHolder> lock_holder{ BaseObject::FromJSObject<LockHolder>(info.Data())}; std::shared_ptr<Lock> lock = lock_holder->lock(); LockManager::GetCurrent()->ReleaseLockAndProcessQueue( env, lock, info[0], true); } // Called when the promise returned from the user's callback resolves static void OnIfAvailableFulfill(const FunctionCallbackInfo<Value>& info) { HandleScope handle_scope(info.GetIsolate()); USE(info.Data().As<Promise::Resolver>()->Resolve( info.GetIsolate()->GetCurrentContext(), info[0])); } // Called when the promise returned from the user's callback rejects static void OnIfAvailableReject(const FunctionCallbackInfo<Value>& info) { USE(info.Data().As<Promise::Resolver>()->Reject( info.GetIsolate()->GetCurrentContext(), info[0])); } void LockManager::CleanupStolenLocks(Environment* env) { std::vector<std::u16string> resources_to_clean; // Iterate held locks and remove entries that were stolen from other envs. { Mutex::ScopedLock scoped_lock(mutex_); for (auto resource_iter = held_locks_.begin(); resource_iter != held_locks_.end(); ++resource_iter) { auto& resource_locks = resource_iter->second; bool has_stolen_from_other_env = false; // Check if this resource has stolen locks from other environments for (const auto& lock_ptr : resource_locks) { if (lock_ptr->is_stolen() && lock_ptr->env() != env) { has_stolen_from_other_env = true; break; } } if (has_stolen_from_other_env) { resources_to_clean.push_back(resource_iter->first); } } } // Clean up resources for (const auto& resource_name : resources_to_clean) { Mutex::ScopedLock scoped_lock(mutex_); auto resource_iter = held_locks_.find(resource_name); if (resource_iter != held_locks_.end()) { auto& resource_locks = resource_iter->second; // Remove stolen locks from other environments for (auto lock_iter = resource_locks.begin(); lock_iter != resource_locks.end();) { if ((*lock_iter)->is_stolen() && (*lock_iter)->env() != env) { lock_iter = resource_locks.erase(lock_iter); } else { ++lock_iter; } } if (resource_locks.empty()) { held_locks_.erase(resource_iter); } } } } /** * Web Locks algorithm implementation * https://w3c.github.io/web-locks/#algorithms */ void LockManager::ProcessQueue(Environment* env) { Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); Local<Context> context = env->context(); // Remove locks that were stolen from this Environment first CleanupStolenLocks(env); while (true) { std::unique_ptr<LockRequest> grantable_request; std::unique_ptr<LockRequest> if_available_request; std::unordered_set<Environment*> other_envs_to_wake; /** * First pass over pending_queue_ * 1- Build first_seen_for_resource: the oldest pending request * for every resource name we encounter * 2- Decide what to do with each entry: * – If it belongs to another Environment, remember that env so we * can wake it later * – For our Environment, pick one of: * * grantable_request – can be granted now * * if_available_request – user asked for ifAvailable and the * resource is currently busy * * otherwise we skip and keep scanning */ { std::unordered_map<std::u16string, LockRequest*> first_seen_for_resource; Mutex::ScopedLock scoped_lock(mutex_); for (auto queue_iter = pending_queue_.begin(); queue_iter != pending_queue_.end(); ++queue_iter) { LockRequest* request = queue_iter->get(); // Collect unique environments to wake up later if (request->env() != env) { other_envs_to_wake.insert(request->env()); } // During a single pass, the first time we see a resource name is the // earliest pending request auto& first_for_resource = first_seen_for_resource[request->name()]; if (first_for_resource == nullptr) { first_for_resource = request; // Mark as first seen for this resource } bool has_earlier_request_for_same_resource = (first_for_resource != request); bool should_wait_for_earlier_requests = false; if (has_earlier_request_for_same_resource) { // Check if this request is compatible with the earliest pending // request first_for_resource if (request->mode() == Lock::Mode::Exclusive || first_for_resource->mode() == Lock::Mode::Exclusive) { // Exclusive locks are incompatible with everything should_wait_for_earlier_requests = true; } // If both are shared, they're compatible and can proceed } // Only process requests from the current environment if (request->env() != env) { continue; } if (should_wait_for_earlier_requests || !IsGrantable(request)) { if (request->if_available()) { // ifAvailable request when resource not available: grant with null if_available_request = std::move(*queue_iter); pending_queue_.erase(queue_iter); break; } continue; } // Found a request that can be granted normally grantable_request = std::move(*queue_iter); pending_queue_.erase(queue_iter); break; } } // Wake each environment only once for (Environment* target_env : other_envs_to_wake) { WakeEnvironment(target_env); } /** * 1- We call the user callback immediately with `null` to signal * that the lock was not granted - Check wrapCallback function in * locks.js 2- Depending on what the callback returns we settle the two * internal promises * 3- No lock is added to held_locks_ in this path, so nothing to * remove later */ if (if_available_request) { Local<Value> null_arg = Null(isolate); Local<Value> callback_result; { TryCatchScope try_catch_scope(env); if (!if_available_request->callback() ->Call(context, Undefined(isolate), 1, &null_arg) .ToLocal(&callback_result)) { // We don't really need to check the return value here since // we're returning early in either case. USE(RejectBoth(context, if_available_request->waiting_promise(), if_available_request->released_promise(), try_catch_scope.Exception())); return; } } if (callback_result->IsPromise()) { Local<Promise> p = callback_result.As<Promise>(); Local<Function> on_fulfilled; Local<Function> on_rejected; CHECK(Function::New(context, OnIfAvailableFulfill, if_available_request->released_promise()) .ToLocal(&on_fulfilled)); CHECK(Function::New(context, OnIfAvailableReject, if_available_request->released_promise()) .ToLocal(&on_rejected)); { TryCatchScope try_catch_scope(env); if (p->Then(context, on_fulfilled, on_rejected).IsEmpty()) { if (!try_catch_scope.CanContinue()) return; Local<Value> err_val; if (try_catch_scope.HasCaught() && !try_catch_scope.Exception().IsEmpty()) { err_val = try_catch_scope.Exception(); } else { err_val = Exception::Error(FIXED_ONE_BYTE_STRING( isolate, "Failed to attach promise handlers")); } USE(RejectBoth(context, if_available_request->waiting_promise(), if_available_request->released_promise(), err_val)); return; } } // After handlers are attached, resolve waiting_promise with the // promise. USE(if_available_request->waiting_promise() ->Resolve(context, p) .IsNothing()); return; } // Non-promise callback result: settle both promises right away. if (if_available_request->waiting_promise() ->Resolve(context, callback_result) .IsNothing()) { return; } USE(if_available_request->released_promise() ->Resolve(context, callback_result) .IsNothing()); return; } if (!grantable_request) return; /** * 1- We grant the lock immediately even if other envs hold it * 2- All existing locks with the same name are marked stolen, their * released_promise is rejected, and their owners are woken so they * can observe the rejection * 3- We remove stolen locks that belong to this env right away; other * envs will clean up in their next queue pass */ if (grantable_request->steal()) { std::unordered_set<Environment*> envs_to_notify; { Mutex::ScopedLock scoped_lock(mutex_); auto held_locks_iter = held_locks_.find(grantable_request->name()); if (held_locks_iter != held_locks_.end()) { // Mark existing locks as stolen and collect environments to notify for (auto& existing_lock : held_locks_iter->second) { existing_lock->mark_stolen(); envs_to_notify.insert(existing_lock->env()); Local<Value> error = Exception::Error(FIXED_ONE_BYTE_STRING(isolate, "LOCK_STOLEN")); if (existing_lock->released_promise() ->Reject(context, error) .IsNothing()) return; } // Remove stolen locks from current environment immediately for (auto lock_iter = held_locks_iter->second.begin(); lock_iter != held_locks_iter->second.end();) { if ((*lock_iter)->env() == env) { lock_iter = held_locks_iter->second.erase(lock_iter); } else { ++lock_iter; } } if (held_locks_iter->second.empty()) { held_locks_.erase(held_locks_iter); } } } // Wake other environments for (Environment* target_env : envs_to_notify) { if (target_env != env) { WakeEnvironment(target_env); } } } // Create and store the new granted lock auto granted_lock = std::make_shared<Lock>(env, grantable_request->name(), grantable_request->mode(), grantable_request->client_id(), grantable_request->waiting_promise(), grantable_request->released_promise()); { Mutex::ScopedLock scoped_lock(mutex_); held_locks_[grantable_request->name()].push_back(granted_lock); } // Create and store the new granted lock Local<Object> lock_info; if (!CreateLockInfoObject(env, *grantable_request).ToLocal(&lock_info)) { return; } // Call user callback Local<Value> callback_arg = lock_info; Local<Value> callback_result; { TryCatchScope try_catch_scope(env); if (!grantable_request->callback() ->Call(context, Undefined(isolate), 1, &callback_arg) .ToLocal(&callback_result)) { // We don't really need to check the return value here since // we're returning early in either case. USE(RejectBoth(context, grantable_request->waiting_promise(), grantable_request->released_promise(), try_catch_scope.Exception())); return; } } // Create LockHolder BaseObjects to safely manage the lock's lifetime // until the user's callback promise settles. auto lock_resolve_holder = LockHolder::Create(env, granted_lock); auto lock_reject_holder = LockHolder::Create(env, granted_lock); Local<Function> on_fulfilled_callback; Local<Function> on_rejected_callback; // Create fulfilled callback first if (!Function::New( context, OnLockCallbackFulfilled, lock_resolve_holder->object()) .ToLocal(&on_fulfilled_callback)) { return; } // Create rejected callback second if (!Function::New( context, OnLockCallbackRejected, lock_reject_holder->object()) .ToLocal(&on_rejected_callback)) { return; } // Handle promise chain if (callback_result->IsPromise()) { Local<Promise> promise = callback_result.As<Promise>(); { TryCatchScope try_catch_scope(env); if (promise->Then(context, on_fulfilled_callback, on_rejected_callback) .IsEmpty()) { if (!try_catch_scope.CanContinue()) return; Local<Value> err_val; if (try_catch_scope.HasCaught() && !try_catch_scope.Exception().IsEmpty()) { err_val = try_catch_scope.Exception(); } else { err_val = Exception::Error(FIXED_ONE_BYTE_STRING( isolate, "Failed to attach promise handlers")); } USE(RejectBoth(context, grantable_request->waiting_promise(), grantable_request->released_promise(), err_val)); return; } } // Lock granted: waiting_promise resolves now with the promise returned // by the callback; on_fulfilled/on_rejected will release the lock when // that promise settles. if (grantable_request->waiting_promise() ->Resolve(context, callback_result) .IsNothing()) { return; } } else { if (grantable_request->waiting_promise() ->Resolve(context, callback_result) .IsNothing()) { return; } Local<Value> promise_args[] = {callback_result}; if (on_fulfilled_callback ->Call(context, Undefined(isolate), 1, promise_args) .IsEmpty()) { // Callback threw an error, handle it like a rejected promise // The error is already propagated through the TryCatch in the // callback return; } } } } /** * name : string – resource identifier * clientId : string – client identifier * mode : string – lock mode * steal : boolean – whether to steal existing locks * ifAvailable : boolean – only grant if immediately available * callback : Function - JS callback */ void LockManager::Request(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); HandleScope scope(isolate); Local<Context> context = env->context(); CHECK_EQ(args.Length(), 6); CHECK(args[0]->IsString()); // name CHECK(args[1]->IsString()); // clientId CHECK(args[2]->IsString()); // mode CHECK(args[3]->IsBoolean()); // steal CHECK(args[4]->IsBoolean()); // ifAvailable CHECK(args[5]->IsFunction()); // callback TwoByteValue resource_name(isolate, args[0]); Utf8Value client_id(isolate, args[1]); Utf8Value mode(isolate, args[2]); bool steal = args[3]->BooleanValue(isolate); bool if_available = args[4]->BooleanValue(isolate); Local<Function> callback = args[5].As<Function>(); Local<Promise::Resolver> waiting_promise; Local<Promise::Resolver> released_promise; if (!Promise::Resolver::New(context).ToLocal(&waiting_promise) || !Promise::Resolver::New(context).ToLocal(&released_promise)) { return; } // Mark both internal promises as handled to prevent unhandled rejection // warnings waiting_promise->GetPromise()->MarkAsHandled(); released_promise->GetPromise()->MarkAsHandled(); LockManager* manager = GetCurrent(); { Mutex::ScopedLock scoped_lock(manager->mutex_); // Register cleanup hook for the environment only once if (manager->registered_envs_.insert(env).second) { env->AddCleanupHook(LockManager::OnEnvironmentCleanup, env); } auto lock_request = std::make_unique<LockRequest>( env, waiting_promise, released_promise, callback, resource_name.ToU16String(), mode.ToStringView() == "shared" ? Lock::Mode::Shared : Lock::Mode::Exclusive, client_id.ToString(), steal, if_available); // Steal requests get priority by going to front of queue if (steal) { manager->pending_queue_.emplace_front(std::move(lock_request)); } else { manager->pending_queue_.push_back(std::move(lock_request)); } } manager->ProcessQueue(env); args.GetReturnValue().Set(released_promise->GetPromise()); } void LockManager::Query(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); HandleScope scope(isolate); Local<Context> context = env->context(); Local<Promise::Resolver> resolver; if (!Promise::Resolver::New(context).ToLocal(&resolver)) { return; } // Always set the return value first so Javascript gets a promise args.GetReturnValue().Set(resolver->GetPromise()); LocalVector<Value> held_list(isolate); LocalVector<Value> pending_list(isolate); LockManager* manager = GetCurrent(); { Mutex::ScopedLock scoped_lock(manager->mutex_); Local<Object> lock_info; for (const auto& resource_entry : manager->held_locks_) { for (const auto& held_lock : resource_entry.second) { if (held_lock->env() == env) { if (!CreateLockInfoObject(env, *held_lock).ToLocal(&lock_info)) { // There should already be a pending exception scheduled. return; } held_list.push_back(lock_info); } } } for (const auto& pending_request : manager->pending_queue_) { if (pending_request->env() == env) { if (!CreateLockInfoObject(env, *pending_request).ToLocal(&lock_info)) { // There should already be a pending exception scheduled. return; } pending_list.push_back(lock_info); } } } auto tmpl = env->lock_query_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "held", "pending", }; tmpl = DictionaryTemplate::New(isolate, names); env->set_lock_query_template(tmpl); } MaybeLocal<Value> values[] = { Array::New(isolate, held_list.data(), held_list.size()), Array::New(isolate, pending_list.data(), pending_list.size()), }; Local<Object> result; if (NewDictionaryInstance(env->context(), tmpl, values).ToLocal(&result)) { // There's no reason to check IsNothing here since we're just returning. USE(resolver->Resolve(context, result)); } } // Runs after the user callback (or its returned promise) settles. void LockManager::ReleaseLockAndProcessQueue(Environment* env, std::shared_ptr<Lock> lock, Local<Value> callback_result, bool was_rejected) { { Mutex::ScopedLock scoped_lock(mutex_); ReleaseLock(lock.get()); } Local<Context> context = env->context(); // For stolen locks, the released_promise was already rejected when marked as // stolen. if (!lock->is_stolen()) { if (was_rejected) { // Propagate rejection from the user callback if (lock->released_promise() ->Reject(context, callback_result) .IsNothing()) return; } else { // Propagate fulfilment if (lock->released_promise() ->Resolve(context, callback_result) .IsNothing()) return; } } ProcessQueue(env); } // Remove a lock from held_locks_ when it's no longer needed void LockManager::ReleaseLock(Lock* lock) { const std::u16string& resource_name = lock->name(); auto resource_iter = held_locks_.find(resource_name); if (resource_iter == held_locks_.end()) return; auto& resource_locks = resource_iter->second; for (auto lock_iter = resource_locks.begin(); lock_iter != resource_locks.end(); ++lock_iter) { if (lock_iter->get() == lock) { resource_locks.erase(lock_iter); if (resource_locks.empty()) held_locks_.erase(resource_iter); break; } } } // Wakeup of target Environment's event loop void LockManager::WakeEnvironment(Environment* target_env) { if (target_env == nullptr || target_env->is_stopping()) return; // Schedule ProcessQueue in the target Environment on its event loop. target_env->SetImmediateThreadsafe([](Environment* env_to_wake) { if (env_to_wake != nullptr && !env_to_wake->is_stopping()) { LockManager::GetCurrent()->ProcessQueue(env_to_wake); } }); } // Remove all held locks and pending requests that belong to an Environment // that is being destroyed void LockManager::CleanupEnvironment(Environment* env_to_cleanup) { Mutex::ScopedLock scoped_lock(mutex_); // Remove every held lock that belongs to this Environment. for (auto resource_iter = held_locks_.begin(); resource_iter != held_locks_.end();) { auto& resource_locks = resource_iter->second; for (auto lock_iter = resource_locks.begin(); lock_iter != resource_locks.end();) { if ((*lock_iter)->env() == env_to_cleanup) { lock_iter = resource_locks.erase(lock_iter); } else { ++lock_iter; } } if (resource_locks.empty()) { resource_iter = held_locks_.erase(resource_iter); } else { ++resource_iter; } } // Remove every pending request submitted by this Environment. for (auto request_iter = pending_queue_.begin(); request_iter != pending_queue_.end();) { if ((*request_iter)->env() == env_to_cleanup) { request_iter = pending_queue_.erase(request_iter); } else { ++request_iter; } } // Finally, remove it from registered_envs_ registered_envs_.erase(env_to_cleanup); } // Cleanup hook wrapper void LockManager::OnEnvironmentCleanup(void* arg) { Environment* env = static_cast<Environment*>(arg); LockManager::GetCurrent()->CleanupEnvironment(env); } LockManager LockManager::current_; void CreatePerIsolateProperties(IsolateData* isolate_data, Local<ObjectTemplate> target) { Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "request", LockManager::Request); SetMethod(isolate, target, "query", LockManager::Query); PropertyAttribute read_only = static_cast<PropertyAttribute>( PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete); target->Set(FIXED_ONE_BYTE_STRING(isolate, "LOCK_MODE_SHARED"), FIXED_ONE_BYTE_STRING(isolate, "shared"), read_only); target->Set(FIXED_ONE_BYTE_STRING(isolate, "LOCK_MODE_EXCLUSIVE"), FIXED_ONE_BYTE_STRING(isolate, "exclusive"), read_only); target->Set(FIXED_ONE_BYTE_STRING(isolate, "LOCK_STOLEN_ERROR"), FIXED_ONE_BYTE_STRING(isolate, "LOCK_STOLEN"), read_only); } void CreatePerContextProperties(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) {} void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(LockManager::Request); registry->Register(LockManager::Query); registry->Register(OnLockCallbackFulfilled); registry->Register(OnLockCallbackRejected); registry->Register(OnIfAvailableFulfill); registry->Register(OnIfAvailableReject); } void LockHolder::MemoryInfo(node::MemoryTracker* tracker) const { tracker->TrackField("lock", lock_); } BaseObjectPtr<LockHolder> LockHolder::Create(Environment* env, std::shared_ptr<Lock> lock) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } return MakeBaseObject<LockHolder>(env, obj, std::move(lock)); } Local<FunctionTemplate> LockHolder::GetConstructorTemplate(Environment* env) { IsolateData* isolate_data = env->isolate_data(); Local<FunctionTemplate> tmpl = isolate_data->lock_holder_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = isolate_data->isolate(); tmpl = NewFunctionTemplate(isolate, nullptr); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "LockHolder")); tmpl->InstanceTemplate()->SetInternalFieldCount( LockHolder::kInternalFieldCount); isolate_data->set_lock_holder_constructor_template(tmpl); } return tmpl; } } // namespace node::worker::locks NODE_BINDING_CONTEXT_AWARE_INTERNAL( locks, node::worker::locks::CreatePerContextProperties) NODE_BINDING_PER_ISOLATE_INIT(locks, node::worker::locks::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE(locks, node::worker::locks::RegisterExternalReferences)