src: remove custom tracking for SharedArrayBuffers

Remove custom tracking for `SharedArrayBuffer`s and their allocators
and instead let V8 do the tracking of both. This is required starting
in V8 7.9, because lifetime management for `ArrayBuffer::Allocator`s
differs from what was performed previously (i.e. it is no longer
easily possible for one Isolate to release an `ArrayBuffer` and another
to accept it into its own allocator), and the alternative would
have been adapting the `SharedArrayBuffer` tracking logic to also
apply to regular `ArrayBuffer` instances.

Refs: https://github.com/nodejs/node/pull/30044

PR-URL: https://github.com/nodejs/node/pull/30020
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Anna Henningsen
2019-10-22 22:03:53 +02:00
committed by Michaël Zasso
parent 2707efd27b
commit 2bdeb88c27
13 changed files with 58 additions and 335 deletions

View File

@@ -564,7 +564,6 @@
'src/node_zlib.cc',
'src/pipe_wrap.cc',
'src/process_wrap.cc',
'src/sharedarraybuffer_metadata.cc',
'src/signal_wrap.cc',
'src/spawn_sync.cc',
'src/stream_base.cc',
@@ -642,7 +641,6 @@
'src/pipe_wrap.h',
'src/req_wrap.h',
'src/req_wrap-inl.h',
'src/sharedarraybuffer_metadata.h',
'src/spawn_sync.h',
'src/stream_base.h',
'src/stream_base-inl.h',

View File

@@ -279,6 +279,14 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator,
return NewIsolate(&params, event_loop, platform);
}
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform) {
Isolate::CreateParams params;
if (allocator) params.array_buffer_allocator_shared = allocator;
return NewIsolate(&params, event_loop, platform);
}
IsolateData* CreateIsolateData(Isolate* isolate,
uv_loop_t* loop,
MultiIsolatePlatform* platform,

View File

@@ -1038,21 +1038,6 @@ char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
return new_data;
}
void Environment::AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator) {
if (keep_alive_allocators_ == nullptr) {
MultiIsolatePlatform* platform = isolate_data()->platform();
CHECK_NOT_NULL(platform);
keep_alive_allocators_ = new ArrayBufferAllocatorList();
platform->AddIsolateFinishedCallback(isolate(), [](void* data) {
delete static_cast<ArrayBufferAllocatorList*>(data);
}, static_cast<void*>(keep_alive_allocators_));
}
keep_alive_allocators_->insert(allocator);
}
bool Environment::RunWeakRefCleanup() {
isolate()->ClearKeptObjects();

View File

@@ -156,7 +156,6 @@ constexpr size_t kFsStatsBufferLength =
V(contextify_global_private_symbol, "node:contextify:global") \
V(decorated_private_symbol, "node:decorated") \
V(napi_wrapper, "node:napi:wrapper") \
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
// Symbols are per-isolate primitives but Environment proxies them
// for the sake of convenience.
@@ -1250,10 +1249,6 @@ class Environment : public MemoryRetainer {
#endif // HAVE_INSPECTOR
// Only available if a MultiIsolatePlatform is in use.
void AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
std::shared_ptr<v8::ArrayBuffer::Allocator>);
private:
template <typename Fn>
inline void CreateImmediate(Fn&& cb,
@@ -1433,10 +1428,6 @@ class Environment : public MemoryRetainer {
// Used by embedders to shutdown running Node instance.
AsyncRequest thread_stopper_;
typedef std::unordered_set<std::shared_ptr<v8::ArrayBuffer::Allocator>>
ArrayBufferAllocatorList;
ArrayBufferAllocatorList* keep_alive_allocators_ = nullptr;
template <typename T>
void ForEachBaseObject(T&& iterator);

View File

@@ -109,6 +109,16 @@ void MemoryTracker::TrackField(const char* edge_name,
TrackField(edge_name, value.get(), node_name);
}
template <typename T>
void MemoryTracker::TrackField(const char* edge_name,
const std::shared_ptr<T>& value,
const char* node_name) {
if (value.get() == nullptr) {
return;
}
TrackField(edge_name, value.get(), node_name);
}
template <typename T, typename Iterator>
void MemoryTracker::TrackField(const char* edge_name,
const T& value,
@@ -206,6 +216,12 @@ void MemoryTracker::TrackField(const char* edge_name,
TrackFieldWithSize(edge_name, value.size, "MallocedBuffer");
}
void MemoryTracker::TrackField(const char* edge_name,
const v8::BackingStore* value,
const char* node_name) {
TrackFieldWithSize(edge_name, value->ByteLength(), "BackingStore");
}
void MemoryTracker::TrackField(const char* name,
const uv_buf_t& value,
const char* node_name) {

View File

@@ -138,6 +138,10 @@ class MemoryTracker {
inline void TrackField(const char* edge_name,
const std::unique_ptr<T>& value,
const char* node_name = nullptr);
template <typename T>
inline void TrackField(const char* edge_name,
const std::shared_ptr<T>& value,
const char* node_name = nullptr);
// For containers, the elements will be graphed as grandchildren nodes
// if the container is not empty.
@@ -197,6 +201,9 @@ class MemoryTracker {
inline void TrackField(const char* edge_name,
const MallocedBuffer<T>& value,
const char* node_name = nullptr);
inline void TrackField(const char* edge_name,
const v8::BackingStore* value,
const char* node_name = nullptr);
// We do not implement CleanupHookCallback as MemoryRetainer
// but instead specialize the method here to avoid the cost of
// virtual pointers.

View File

@@ -328,6 +328,10 @@ NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator,
NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform);
NODE_EXTERN v8::Isolate* NewIsolate(
std::shared_ptr<ArrayBufferAllocator> allocator,
struct uv_loop_s* event_loop,
MultiIsolatePlatform* platform);
// Creates a new context with Node.js-specific tweaks.
NODE_EXTERN v8::Local<v8::Context> NewContext(

View File

@@ -12,7 +12,7 @@
using node::contextify::ContextifyContext;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferCreationMode;
using v8::BackingStore;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Exception;
@@ -123,10 +123,9 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
std::vector<Local<SharedArrayBuffer>> shared_array_buffers;
// Attach all transferred SharedArrayBuffers to their new Isolate.
for (uint32_t i = 0; i < shared_array_buffers_.size(); ++i) {
Local<SharedArrayBuffer> sab;
if (!shared_array_buffers_[i]->GetSharedArrayBuffer(env, context)
.ToLocal(&sab))
return MaybeLocal<Value>();
Local<SharedArrayBuffer> sab =
SharedArrayBuffer::New(env->isolate(),
std::move(shared_array_buffers_[i]));
shared_array_buffers.push_back(sab);
}
shared_array_buffers_.clear();
@@ -141,30 +140,12 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
delegate.deserializer = &deserializer;
// Attach all transferred ArrayBuffers to their new Isolate.
for (uint32_t i = 0; i < array_buffer_contents_.size(); ++i) {
if (!env->isolate_data()->uses_node_allocator()) {
// We don't use Node's allocator on the receiving side, so we have
// to create the ArrayBuffer from a copy of the memory.
AllocatedBuffer buf =
env->AllocateManaged(array_buffer_contents_[i].size);
memcpy(buf.data(),
array_buffer_contents_[i].data,
array_buffer_contents_[i].size);
deserializer.TransferArrayBuffer(i, buf.ToArrayBuffer());
continue;
}
env->isolate_data()->node_allocator()->RegisterPointer(
array_buffer_contents_[i].data, array_buffer_contents_[i].size);
for (uint32_t i = 0; i < array_buffers_.size(); ++i) {
Local<ArrayBuffer> ab =
ArrayBuffer::New(env->isolate(),
array_buffer_contents_[i].release(),
array_buffer_contents_[i].size,
ArrayBufferCreationMode::kInternalized);
ArrayBuffer::New(env->isolate(), std::move(array_buffers_[i]));
deserializer.TransferArrayBuffer(i, ab);
}
array_buffer_contents_.clear();
array_buffers_.clear();
if (deserializer.ReadHeader(context).IsNothing())
return MaybeLocal<Value>();
@@ -173,8 +154,8 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
}
void Message::AddSharedArrayBuffer(
const SharedArrayBufferMetadataReference& reference) {
shared_array_buffers_.push_back(reference);
std::shared_ptr<BackingStore> backing_store) {
shared_array_buffers_.emplace_back(std::move(backing_store));
}
void Message::AddMessagePort(std::unique_ptr<MessagePortData>&& data) {
@@ -249,16 +230,9 @@ class SerializerDelegate : public ValueSerializer::Delegate {
}
}
auto reference = SharedArrayBufferMetadata::ForSharedArrayBuffer(
env_,
context_,
shared_array_buffer);
if (!reference) {
return Nothing<uint32_t>();
}
seen_shared_array_buffers_.emplace_back(
Global<SharedArrayBuffer> { isolate, shared_array_buffer });
msg_->AddSharedArrayBuffer(reference);
msg_->AddSharedArrayBuffer(shared_array_buffer->GetBackingStore());
return Just(i);
}
@@ -386,18 +360,12 @@ Maybe<bool> Message::Serialize(Environment* env,
}
for (Local<ArrayBuffer> ab : array_buffers) {
// If serialization succeeded, we want to take ownership of
// (a.k.a. externalize) the underlying memory region and render
// it inaccessible in this Isolate.
ArrayBuffer::Contents contents = ab->Externalize();
// If serialization succeeded, we render it inaccessible in this Isolate.
std::shared_ptr<BackingStore> backing_store = ab->GetBackingStore();
ab->Externalize(backing_store);
ab->Detach();
CHECK(env->isolate_data()->uses_node_allocator());
env->isolate_data()->node_allocator()->UnregisterPointer(
contents.Data(), contents.ByteLength());
array_buffer_contents_.emplace_back(MallocedBuffer<char>{
static_cast<char*>(contents.Data()), contents.ByteLength()});
array_buffers_.emplace_back(std::move(backing_store));
}
delegate.Finish();
@@ -411,9 +379,8 @@ Maybe<bool> Message::Serialize(Environment* env,
}
void Message::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("array_buffer_contents", array_buffer_contents_);
tracker->TrackFieldWithSize("shared_array_buffers",
shared_array_buffers_.size() * sizeof(shared_array_buffers_[0]));
tracker->TrackField("array_buffers_", array_buffers_);
tracker->TrackField("shared_array_buffers", shared_array_buffers_);
tracker->TrackField("message_ports", message_ports_);
}

View File

@@ -5,7 +5,6 @@
#include "env.h"
#include "node_mutex.h"
#include "sharedarraybuffer_metadata.h"
#include <list>
namespace node {
@@ -52,7 +51,7 @@ class Message : public MemoryRetainer {
// Internal method of Message that is called when a new SharedArrayBuffer
// object is encountered in the incoming value's structure.
void AddSharedArrayBuffer(const SharedArrayBufferMetadataReference& ref);
void AddSharedArrayBuffer(std::shared_ptr<v8::BackingStore> backing_store);
// Internal method of Message that is called once serialization finishes
// and that transfers ownership of `data` to this message.
void AddMessagePort(std::unique_ptr<MessagePortData>&& data);
@@ -74,8 +73,8 @@ class Message : public MemoryRetainer {
private:
MallocedBuffer<char> main_message_buf_;
std::vector<MallocedBuffer<char>> array_buffer_contents_;
std::vector<SharedArrayBufferMetadataReference> shared_array_buffers_;
std::vector<std::shared_ptr<v8::BackingStore>> array_buffers_;
std::vector<std::shared_ptr<v8::BackingStore>> shared_array_buffers_;
std::vector<std::unique_ptr<MessagePortData>> message_ports_;
std::vector<v8::WasmModuleObject::TransferrableModule> wasm_modules_;

View File

@@ -51,7 +51,6 @@ Worker::Worker(Environment* env,
per_isolate_opts_(per_isolate_opts),
exec_argv_(exec_argv),
platform_(env->isolate_data()->platform()),
array_buffer_allocator_(ArrayBufferAllocator::Create()),
start_profiler_idle_notifier_(env->profiler_idle_notifier_started()),
thread_id_(Environment::AllocateThreadId()),
env_vars_(env->env_vars()) {
@@ -95,10 +94,6 @@ bool Worker::is_stopped() const {
return stopped_;
}
std::shared_ptr<ArrayBufferAllocator> Worker::array_buffer_allocator() {
return array_buffer_allocator_;
}
void Worker::UpdateResourceConstraints(ResourceConstraints* constraints) {
constraints->set_stack_limit(reinterpret_cast<uint32_t*>(stack_base_));
@@ -138,9 +133,11 @@ class WorkerThreadData {
: w_(w) {
CHECK_EQ(uv_loop_init(&loop_), 0);
std::shared_ptr<ArrayBufferAllocator> allocator =
ArrayBufferAllocator::Create();
Isolate::CreateParams params;
SetIsolateCreateParamsForNode(&params);
params.array_buffer_allocator = w->array_buffer_allocator_.get();
params.array_buffer_allocator_shared = allocator;
w->UpdateResourceConstraints(&params.constraints);
@@ -164,7 +161,7 @@ class WorkerThreadData {
isolate_data_.reset(CreateIsolateData(isolate,
&loop_,
w_->platform_,
w->array_buffer_allocator_.get()));
allocator.get()));
CHECK(isolate_data_);
if (w_->per_isolate_opts_)
isolate_data_->set_options(std::move(w_->per_isolate_opts_));

View File

@@ -48,7 +48,6 @@ class Worker : public AsyncWrap {
SET_SELF_SIZE(Worker)
bool is_stopped() const;
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator();
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CloneParentEnvVars(
@@ -72,7 +71,6 @@ class Worker : public AsyncWrap {
std::vector<std::string> argv_;
MultiIsolatePlatform* platform_;
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator_;
v8::Isolate* isolate_ = nullptr;
bool start_profiler_idle_notifier_;
uv_thread_t tid_;

View File

@@ -1,175 +0,0 @@
#include "sharedarraybuffer_metadata.h"
#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "node_worker.h"
#include "util-inl.h"
#include <utility>
using v8::Context;
using v8::Function;
using v8::FunctionTemplate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::SharedArrayBuffer;
using v8::Value;
namespace node {
namespace worker {
namespace {
// Yield a JS constructor for SABLifetimePartner objects in the form of a
// standard API object, that has a single field for containing the raw
// SABLifetimePartner* pointer.
Local<Function> GetSABLifetimePartnerConstructor(
Environment* env, Local<Context> context) {
Local<FunctionTemplate> templ;
templ = env->sab_lifetimepartner_constructor_template();
if (!templ.IsEmpty())
return templ->GetFunction(context).ToLocalChecked();
templ = BaseObject::MakeLazilyInitializedJSTemplate(env);
templ->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(),
"SABLifetimePartner"));
env->set_sab_lifetimepartner_constructor_template(templ);
return GetSABLifetimePartnerConstructor(env, context);
}
class SABLifetimePartner : public BaseObject {
public:
SABLifetimePartner(Environment* env,
Local<Object> obj,
SharedArrayBufferMetadataReference r)
: BaseObject(env, obj),
reference(std::move(r)) {
MakeWeak();
env->AddCleanupHook(CleanupHook, static_cast<void*>(this));
}
~SABLifetimePartner() {
env()->RemoveCleanupHook(CleanupHook, static_cast<void*>(this));
}
static void CleanupHook(void* data) {
// There is another cleanup hook attached to this object because it is a
// BaseObject. Cleanup hooks are triggered in reverse order of addition,
// so if this object is destroyed through GC, the destructor removes all
// hooks associated with this object, meaning that this cleanup hook
// only runs at the end of the Environments lifetime.
// In that case, V8 still knows about the SharedArrayBuffer and tries to
// free it when the last Isolate with access to it is disposed; for that,
// the ArrayBuffer::Allocator needs to be kept alive longer than this
// object and longer than the Environment instance.
//
// This is a workaround for https://github.com/nodejs/node-v8/issues/115
// (introduced in V8 7.9) and we should be able to remove it once V8
// ArrayBuffer::Allocator refactoring/removal is complete.
SABLifetimePartner* self = static_cast<SABLifetimePartner*>(data);
self->env()->AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
self->reference->allocator());
}
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(SABLifetimePartner)
SET_SELF_SIZE(SABLifetimePartner)
SharedArrayBufferMetadataReference reference;
};
} // anonymous namespace
SharedArrayBufferMetadataReference
SharedArrayBufferMetadata::ForSharedArrayBuffer(
Environment* env,
Local<Context> context,
Local<SharedArrayBuffer> source) {
Local<Value> lifetime_partner;
if (!source->GetPrivate(context,
env->sab_lifetimepartner_symbol())
.ToLocal(&lifetime_partner)) {
return nullptr;
}
if (lifetime_partner->IsObject() &&
env->sab_lifetimepartner_constructor_template()
->HasInstance(lifetime_partner)) {
CHECK(source->IsExternal());
SABLifetimePartner* partner =
Unwrap<SABLifetimePartner>(lifetime_partner.As<Object>());
CHECK_NOT_NULL(partner);
return partner->reference;
}
if (source->IsExternal()) {
// If this is an external SharedArrayBuffer but we do not see a lifetime
// partner object, it was not us who externalized it. In that case, there
// is no way to serialize it, because it's unclear how the memory
// is actually owned.
THROW_ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER(env);
return nullptr;
}
// If the SharedArrayBuffer is coming from a Worker, we need to make sure
// that the corresponding ArrayBuffer::Allocator lives at least as long as
// the SharedArrayBuffer itself.
worker::Worker* w = env->worker_context();
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator =
w != nullptr ? w->array_buffer_allocator() : nullptr;
SharedArrayBuffer::Contents contents = source->Externalize();
SharedArrayBufferMetadataReference r(
new SharedArrayBufferMetadata(contents, allocator));
if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
return nullptr;
return r;
}
Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
Environment* env, Local<Context> context,
Local<SharedArrayBuffer> target) {
CHECK(target->IsExternal());
Local<Function> ctor = GetSABLifetimePartnerConstructor(env, context);
Local<Object> obj;
if (!ctor->NewInstance(context).ToLocal(&obj))
return Nothing<bool>();
new SABLifetimePartner(env, obj, shared_from_this());
return target->SetPrivate(context,
env->sab_lifetimepartner_symbol(),
obj);
}
SharedArrayBufferMetadata::SharedArrayBufferMetadata(
const SharedArrayBuffer::Contents& contents,
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator)
: contents_(contents), allocator_(allocator) { }
SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
contents_.Deleter()(contents_.Data(),
contents_.ByteLength(),
contents_.DeleterData());
}
MaybeLocal<SharedArrayBuffer> SharedArrayBufferMetadata::GetSharedArrayBuffer(
Environment* env, Local<Context> context) {
Local<SharedArrayBuffer> obj =
SharedArrayBuffer::New(env->isolate(),
contents_.Data(),
contents_.ByteLength());
if (AssignToSharedArrayBuffer(env, context, obj).IsNothing())
return MaybeLocal<SharedArrayBuffer>();
return obj;
}
} // namespace worker
} // namespace node

View File

@@ -1,72 +0,0 @@
#ifndef SRC_SHAREDARRAYBUFFER_METADATA_H_
#define SRC_SHAREDARRAYBUFFER_METADATA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node.h"
#include <memory>
namespace node {
namespace worker {
class SharedArrayBufferMetadata;
// This is an object associated with a SharedArrayBuffer, which keeps track
// of a cross-thread reference count. Once a SharedArrayBuffer is transferred
// for the first time (or is attempted to be transferred), one of these objects
// is created, and the SharedArrayBuffer is moved from internalized mode into
// externalized mode (i.e. the JS engine no longer frees the memory on its own).
//
// This will always be referred to using a std::shared_ptr, since it keeps
// a reference count and is guaranteed to be thread-safe.
typedef std::shared_ptr<SharedArrayBufferMetadata>
SharedArrayBufferMetadataReference;
class SharedArrayBufferMetadata
: public std::enable_shared_from_this<SharedArrayBufferMetadata> {
public:
static SharedArrayBufferMetadataReference ForSharedArrayBuffer(
Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::SharedArrayBuffer> source);
~SharedArrayBufferMetadata();
// Create a SharedArrayBuffer object for a specific Environment and Context.
// The created SharedArrayBuffer will be in externalized mode and has
// a hidden object attached to it, during whose lifetime the reference
// count is increased by 1.
v8::MaybeLocal<v8::SharedArrayBuffer> GetSharedArrayBuffer(
Environment* env, v8::Local<v8::Context> context);
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator() const {
return allocator_;
}
SharedArrayBufferMetadata(SharedArrayBufferMetadata&& other) = delete;
SharedArrayBufferMetadata& operator=(
SharedArrayBufferMetadata&& other) = delete;
SharedArrayBufferMetadata& operator=(
const SharedArrayBufferMetadata&) = delete;
SharedArrayBufferMetadata(const SharedArrayBufferMetadata&) = delete;
private:
SharedArrayBufferMetadata(
const v8::SharedArrayBuffer::Contents&,
std::shared_ptr<v8::ArrayBuffer::Allocator>);
// Attach a lifetime tracker object with a reference count to `target`.
v8::Maybe<bool> AssignToSharedArrayBuffer(
Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::SharedArrayBuffer> target);
v8::SharedArrayBuffer::Contents contents_;
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator_;
};
} // namespace worker
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_SHAREDARRAYBUFFER_METADATA_H_