diff --git a/node.gyp b/node.gyp index fcdcd696e9..3bbc9158c4 100644 --- a/node.gyp +++ b/node.gyp @@ -224,6 +224,7 @@ 'src/node_i18n.cc', 'src/pipe_wrap.cc', 'src/process_wrap.cc', + 'src/sharedarraybuffer-metadata.cc', 'src/signal_wrap.cc', 'src/spawn_sync.cc', 'src/string_bytes.cc', @@ -279,6 +280,7 @@ 'src/udp_wrap.h', 'src/req-wrap.h', 'src/req-wrap-inl.h', + 'src/sharedarraybuffer-metadata.h', 'src/string_bytes.h', 'src/stream_base.h', 'src/stream_base-inl.h', diff --git a/src/env.h b/src/env.h index 3288f56d80..0d53e2de0d 100644 --- a/src/env.h +++ b/src/env.h @@ -92,6 +92,7 @@ class ModuleWrap; V(decorated_private_symbol, "node:decorated") \ V(npn_buffer_private_symbol, "node:npnBuffer") \ V(processed_private_symbol, "node:processed") \ + V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \ V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \ V(domain_private_symbol, "node:domain") \ @@ -324,6 +325,7 @@ class ModuleWrap; V(promise_wrap_template, v8::ObjectTemplate) \ V(push_values_to_array_function, v8::Function) \ V(randombytes_constructor_template, v8::ObjectTemplate) \ + V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \ V(script_context_constructor_template, v8::FunctionTemplate) \ V(script_data_constructor_function, v8::Function) \ V(secure_context_constructor_template, v8::FunctionTemplate) \ diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 8715447e7e..b6d6fdbf5b 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -25,6 +25,7 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Object; +using v8::SharedArrayBuffer; using v8::String; using v8::Value; using v8::ValueDeserializer; @@ -55,6 +56,7 @@ Message& Message::operator=(Message&& other) { main_message_buf_ = other.main_message_buf_; other.main_message_buf_ = uv_buf_init(nullptr, 0); array_buffer_contents_ = std::move(other.array_buffer_contents_); + shared_array_buffers_ = std::move(other.shared_array_buffers_); message_ports_ = std::move(other.message_ports_); return *this; } @@ -98,6 +100,7 @@ MaybeLocal Message::Deserialize(Environment* env, // This is for messages generated in C++ with the expectation that they // are handled in JS, e.g. serialized error messages from workers. CHECK(array_buffer_contents_.empty()); + CHECK(shared_array_buffers_.empty()); CHECK(message_ports_.empty()); char* buf = main_message_buf_.base; main_message_buf_.base = nullptr; @@ -137,12 +140,26 @@ MaybeLocal Message::Deserialize(Environment* env, } array_buffer_contents_.clear(); + for (uint32_t i = 0; i < shared_array_buffers_.size(); ++i) { + Local sab; + if (!shared_array_buffers_[i]->GetSharedArrayBuffer(env, context) + .ToLocal(&sab)) + return MaybeLocal(); + deserializer.TransferSharedArrayBuffer(i, sab); + } + shared_array_buffers_.clear(); + if (deserializer.ReadHeader(context).IsNothing()) return MaybeLocal(); return handle_scope.Escape( deserializer.ReadValue(context).FromMaybe(Local())); } +void Message::AddSharedArrayBuffer( + SharedArrayBufferMetadataReference reference) { + shared_array_buffers_.push_back(reference); +} + void Message::AddMessagePort(std::unique_ptr&& data) { message_ports_.emplace_back(std::move(data)); } @@ -167,6 +184,26 @@ class SerializerDelegate : public ValueSerializer::Delegate { return Nothing(); } + Maybe GetSharedArrayBufferId( + Isolate* isolate, Local shared_array_buffer) override { + uint32_t i; + for (i = 0; i < seen_shared_array_buffers_.size(); ++i) { + if (seen_shared_array_buffers_[i] == shared_array_buffer) + return Just(i); + } + + SharedArrayBufferMetadataReference reference( + SharedArrayBufferMetadata::ForIncomingSharedArrayBuffer(env_, + context_, + shared_array_buffer)); + if (!reference) { + return Nothing(); + } + seen_shared_array_buffers_.push_back(shared_array_buffer); + msg_->AddSharedArrayBuffer(reference); + return Just(i); + } + void Finish() { for (MessagePort* port : ports_) { port->Close(); @@ -192,6 +229,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { Environment* env_; Local context_; Message* msg_; + std::vector> seen_shared_array_buffers_; std::vector ports_; friend class worker::Message; diff --git a/src/node_messaging.h b/src/node_messaging.h index 999d7e577a..fdff9eee98 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -5,8 +5,8 @@ #include "env.h" #include "node_mutex.h" +#include "sharedarraybuffer-metadata.h" #include -#include namespace node { namespace worker { @@ -27,7 +27,6 @@ class MessagePort; // Any further flagged message codes are defined by the modules that use them. - // Represents a single communication message. The only non-standard extension // here is passing of a separate flag that the Workers implementation uses // for internal cross-thread information passing. @@ -54,6 +53,9 @@ class Message { v8::Local input, v8::Local transfer_list); + // Internal method of Message that is called when a new SharedArrayBuffer + // object is encountered in the incoming value's structure. + void AddSharedArrayBuffer(SharedArrayBufferMetadataReference ref); // Internal method of Message that is called once serialization finishes // and that transfers ownership of `data` to this message. void AddMessagePort(std::unique_ptr&& data); @@ -62,6 +64,7 @@ class Message { int32_t flag_ = MESSAGE_FLAG_NONE; uv_buf_t main_message_buf_; std::vector array_buffer_contents_; + std::vector shared_array_buffers_; std::vector> message_ports_; friend class MessagePort; diff --git a/src/sharedarraybuffer-metadata.cc b/src/sharedarraybuffer-metadata.cc new file mode 100644 index 0000000000..3af1f62c14 --- /dev/null +++ b/src/sharedarraybuffer-metadata.cc @@ -0,0 +1,137 @@ +#include "sharedarraybuffer-metadata.h" +#include "base-object.h" +#include "base-object-inl.h" + +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +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 +// SABLiftimePartner* pointer. +Local GetSABLifetimePartnerConstructor( + Environment* env, Local context) { + Local templ; + templ = env->sab_lifetimepartner_constructor_template(); + if (!templ.IsEmpty()) + return templ->GetFunction(context).ToLocalChecked(); + + { + Local m = env->NewFunctionTemplate( + [](const FunctionCallbackInfo& info) { + CHECK(info.IsConstructCall()); + }); + m->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), + "SABLifetimePartner")); + m->InstanceTemplate()->SetInternalFieldCount(1); + + env->set_sab_lifetimepartner_constructor_template(m); + } + + return GetSABLifetimePartnerConstructor(env, context); +} + +class SABLifetimePartner : public BaseObject { + public: + SABLifetimePartner(Environment* env, + Local obj, + SharedArrayBufferMetadataReference r) + : BaseObject(env, obj), + reference(r) { + MakeWeak(this); + } + + SharedArrayBufferMetadataReference reference; +}; + +} // anonymous namespace + +SharedArrayBufferMetadataReference +SharedArrayBufferMetadata::ForIncomingSharedArrayBuffer( + Environment* env, Local context, Local source) { + Local 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)) { + if (!source->IsExternal()) { + env->ThrowError("Found internalized SharedArrayBuffer with " + "lifetime partner object"); + return nullptr; + } + + SABLifetimePartner* partner = + Unwrap(lifetime_partner.As()); + CHECK_NE(partner, nullptr); + return partner->reference; + } + + if (source->IsExternal()) { + // If this is an external SharedArrayBuffer but we do not see a lifetime + // partner object, we did not externalize it. In that case, there is no + // way to serialize it. + env->ThrowError("Cannot serialize externalized SharedArrayBuffer"); + return nullptr; + } + + SharedArrayBuffer::Contents contents = source->Externalize(); + SharedArrayBufferMetadataReference r(new SharedArrayBufferMetadata( + contents.Data(), contents.ByteLength())); + if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing()) + return nullptr; + return r; +} + +Maybe SharedArrayBufferMetadata::AssignToSharedArrayBuffer( + Environment* env, Local context, + Local target) { + Local ctor = GetSABLifetimePartnerConstructor(env, context); + Local obj; + if (!ctor->NewInstance(context).ToLocal(&obj)) + return Nothing(); + + new SABLifetimePartner(env, obj, shared_from_this()); + return target->SetPrivate(context, + env->sab_lifetimepartner_symbol(), + obj); +} + +SharedArrayBufferMetadata::SharedArrayBufferMetadata(void* data, size_t size) + : data(data), size(size) { } + +SharedArrayBufferMetadata::~SharedArrayBufferMetadata() { + free(data); +} + +MaybeLocal SharedArrayBufferMetadata::GetSharedArrayBuffer( + Environment* env, Local context) { + Local obj = + SharedArrayBuffer::New(env->isolate(), data, size); + + if (AssignToSharedArrayBuffer(env, context, obj).IsNothing()) + return MaybeLocal(); + + return obj; +} + +} // namespace worker +} // namespace node diff --git a/src/sharedarraybuffer-metadata.h b/src/sharedarraybuffer-metadata.h new file mode 100644 index 0000000000..af5225d452 --- /dev/null +++ b/src/sharedarraybuffer-metadata.h @@ -0,0 +1,66 @@ +#ifndef SRC_SHAREDARRAYBUFFER_METADATA_H_ +#define SRC_SHAREDARRAYBUFFER_METADATA_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include + +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 + SharedArrayBufferMetadataReference; + +class SharedArrayBufferMetadata + : public std::enable_shared_from_this { + public: + static SharedArrayBufferMetadataReference ForIncomingSharedArrayBuffer( + Environment* env, v8::Local context, + v8::Local 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 GetSharedArrayBuffer( + Environment* env, v8::Local context); + + SharedArrayBufferMetadata(SharedArrayBufferMetadata&& other) = delete; + SharedArrayBufferMetadata& operator=( + SharedArrayBufferMetadata&& other) = delete; + SharedArrayBufferMetadata& operator=( + const SharedArrayBufferMetadata&) = delete; + SharedArrayBufferMetadata(const SharedArrayBufferMetadata&) = delete; + + private: + explicit SharedArrayBufferMetadata(void* data, size_t size); + + // Attach a lifetime tracker object with a reference count to `target`. + v8::Maybe AssignToSharedArrayBuffer( + Environment* env, + v8::Local context, + v8::Local target); + + void* data = nullptr; + size_t size = 0; +}; + +} // namespace worker +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + +#endif // SRC_SHAREDARRAYBUFFER_METADATA_H_