blob: 9c871569c4d46b3c5830be52f71a81b459320878 [file] [edit]
#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)