Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d1f372f

Browse filesBrowse files
addaleaxtargos
authored andcommitted
worker: add SharedArrayBuffer sharing
Logic is added to the `MessagePort` mechanism that attaches hidden objects to those instances when they are transferred that track their lifetime and maintain a reference count, to make sure that memory is freed at the appropriate times. Thanks to Stephen Belanger for reviewing this change in its original PR. Refs: ayojs/ayo#106 PR-URL: #20876 Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Shingo Inoue <leko.noor@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
1 parent f447acd commit d1f372f
Copy full SHA for d1f372f

File tree

Expand file treeCollapse file tree

8 files changed

+274
-9
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

8 files changed

+274
-9
lines changed
Open diff view settings
Collapse file

‎doc/api/worker.md‎

Copy file name to clipboardExpand all lines: doc/api/worker.md
+11-4Lines changed: 11 additions & 4 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,16 @@ to stringify.
8585

8686
`transferList` may be a list of `ArrayBuffer` and `MessagePort` objects.
8787
After transferring, they will not be usable on the sending side of the channel
88-
anymore (even if they are not contained in `value`).
88+
anymore (even if they are not contained in `value`). Unlike with
89+
[child processes][], transferring handles such as network sockets is currently
90+
not supported.
91+
92+
If `value` contains [`SharedArrayBuffer`][] instances, those will be accessible
93+
from either thread. They cannot be listed in `transferList`.
8994

9095
`value` may still contain `ArrayBuffer` instances that are not in
9196
`transferList`; in that case, the underlying memory is copied rather than moved.
9297

93-
For more information on the serialization and deserialization mechanisms
94-
behind this API, see the [serialization API of the `v8` module][v8.serdes].
95-
9698
Because the object cloning uses the structured clone algorithm,
9799
non-enumerable properties, property accessors, and object prototypes are
98100
not preserved. In particular, [`Buffer`][] objects will be read as
@@ -101,6 +103,9 @@ plain [`Uint8Array`][]s on the receiving side.
101103
The message object will be cloned immediately, and can be modified after
102104
posting without having side effects.
103105

106+
For more information on the serialization and deserialization mechanisms
107+
behind this API, see the [serialization API of the `v8` module][v8.serdes].
108+
104109
### port.ref()
105110
<!-- YAML
106111
added: REPLACEME
@@ -137,10 +142,12 @@ be `ref()`ed and `unref()`ed automatically depending on whether
137142
listeners for the event exist.
138143

139144
[`Buffer`]: buffer.html
145+
[child processes]: child_process.html
140146
[`EventEmitter`]: events.html
141147
[`MessagePort`]: #worker_class_messageport
142148
[`port.postMessage()`]: #worker_port_postmessage_value_transferlist
143149
[v8.serdes]: v8.html#v8_serialization_api
150+
[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
144151
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
145152
[browser `MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
146153
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
Collapse file

‎node.gyp‎

Copy file name to clipboardExpand all lines: node.gyp
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@
352352
'src/node_i18n.cc',
353353
'src/pipe_wrap.cc',
354354
'src/process_wrap.cc',
355+
'src/sharedarraybuffer_metadata.cc',
355356
'src/signal_wrap.cc',
356357
'src/spawn_sync.cc',
357358
'src/string_bytes.cc',
@@ -411,6 +412,7 @@
411412
'src/udp_wrap.h',
412413
'src/req_wrap.h',
413414
'src/req_wrap-inl.h',
415+
'src/sharedarraybuffer_metadata.h',
414416
'src/string_bytes.h',
415417
'src/string_decoder.h',
416418
'src/string_decoder-inl.h',
Collapse file

‎src/env.h‎

Copy file name to clipboardExpand all lines: src/env.h
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ struct PackageConfig {
106106
V(decorated_private_symbol, "node:decorated") \
107107
V(napi_env, "node:napi:env") \
108108
V(napi_wrapper, "node:napi:wrapper") \
109+
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
109110

110111
// Symbols are per-isolate primitives but Environment proxies them
111112
// for the sake of convenience.
@@ -338,6 +339,7 @@ struct PackageConfig {
338339
V(promise_wrap_template, v8::ObjectTemplate) \
339340
V(push_values_to_array_function, v8::Function) \
340341
V(randombytes_constructor_template, v8::ObjectTemplate) \
342+
V(sab_lifetimepartner_constructor_template, v8::FunctionTemplate) \
341343
V(script_context_constructor_template, v8::FunctionTemplate) \
342344
V(script_data_constructor_function, v8::Function) \
343345
V(secure_context_constructor_template, v8::FunctionTemplate) \
Collapse file

‎src/node_errors.h‎

Copy file name to clipboardExpand all lines: src/node_errors.h
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace node {
3131
V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, TypeError) \
3232
V(ERR_MISSING_MODULE, Error) \
3333
V(ERR_STRING_TOO_LONG, Error) \
34+
V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, TypeError) \
3435

3536
#define V(code, type) \
3637
inline v8::Local<v8::Value> code(v8::Isolate* isolate, \
@@ -60,7 +61,9 @@ namespace node {
6061
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
6162
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
6263
V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \
63-
"MessagePort was found in message but not listed in transferList")
64+
"MessagePort was found in message but not listed in transferList") \
65+
V(ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER, \
66+
"Cannot serialize externalized SharedArrayBuffer") \
6467

6568
#define V(code, message) \
6669
inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \
Collapse file

‎src/node_messaging.cc‎

Copy file name to clipboardExpand all lines: src/node_messaging.cc
+54-3Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ using v8::Maybe;
2424
using v8::MaybeLocal;
2525
using v8::Nothing;
2626
using v8::Object;
27+
using v8::SharedArrayBuffer;
2728
using v8::String;
2829
using v8::Value;
2930
using v8::ValueDeserializer;
@@ -43,8 +44,13 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
4344
public:
4445
DeserializerDelegate(Message* m,
4546
Environment* env,
46-
const std::vector<MessagePort*>& message_ports)
47-
: env_(env), msg_(m), message_ports_(message_ports) {}
47+
const std::vector<MessagePort*>& message_ports,
48+
const std::vector<Local<SharedArrayBuffer>>&
49+
shared_array_buffers)
50+
: env_(env),
51+
msg_(m),
52+
message_ports_(message_ports),
53+
shared_array_buffers_(shared_array_buffers) {}
4854

4955
MaybeLocal<Object> ReadHostObject(Isolate* isolate) override {
5056
// Currently, only MessagePort hosts objects are supported, so identifying
@@ -56,12 +62,19 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
5662
return message_ports_[id]->object();
5763
};
5864

65+
MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
66+
Isolate* isolate, uint32_t clone_id) override {
67+
CHECK_LE(clone_id, shared_array_buffers_.size());
68+
return shared_array_buffers_[clone_id];
69+
}
70+
5971
ValueDeserializer* deserializer = nullptr;
6072

6173
private:
6274
Environment* env_;
6375
Message* msg_;
6476
const std::vector<MessagePort*>& message_ports_;
77+
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers_;
6578
};
6679

6780
} // anonymous namespace
@@ -87,7 +100,18 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
87100
}
88101
message_ports_.clear();
89102

90-
DeserializerDelegate delegate(this, env, ports);
103+
std::vector<Local<SharedArrayBuffer>> shared_array_buffers;
104+
// Attach all transfered SharedArrayBuffers to their new Isolate.
105+
for (uint32_t i = 0; i < shared_array_buffers_.size(); ++i) {
106+
Local<SharedArrayBuffer> sab;
107+
if (!shared_array_buffers_[i]->GetSharedArrayBuffer(env, context)
108+
.ToLocal(&sab))
109+
return MaybeLocal<Value>();
110+
shared_array_buffers.push_back(sab);
111+
}
112+
shared_array_buffers_.clear();
113+
114+
DeserializerDelegate delegate(this, env, ports, shared_array_buffers);
91115
ValueDeserializer deserializer(
92116
env->isolate(),
93117
reinterpret_cast<const uint8_t*>(main_message_buf_.data),
@@ -112,6 +136,11 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
112136
deserializer.ReadValue(context).FromMaybe(Local<Value>()));
113137
}
114138

139+
void Message::AddSharedArrayBuffer(
140+
SharedArrayBufferMetadataReference reference) {
141+
shared_array_buffers_.push_back(reference);
142+
}
143+
115144
void Message::AddMessagePort(std::unique_ptr<MessagePortData>&& data) {
116145
message_ports_.emplace_back(std::move(data));
117146
}
@@ -139,6 +168,27 @@ class SerializerDelegate : public ValueSerializer::Delegate {
139168
return Nothing<bool>();
140169
}
141170

171+
Maybe<uint32_t> GetSharedArrayBufferId(
172+
Isolate* isolate,
173+
Local<SharedArrayBuffer> shared_array_buffer) override {
174+
uint32_t i;
175+
for (i = 0; i < seen_shared_array_buffers_.size(); ++i) {
176+
if (seen_shared_array_buffers_[i] == shared_array_buffer)
177+
return Just(i);
178+
}
179+
180+
auto reference = SharedArrayBufferMetadata::ForSharedArrayBuffer(
181+
env_,
182+
context_,
183+
shared_array_buffer);
184+
if (!reference) {
185+
return Nothing<uint32_t>();
186+
}
187+
seen_shared_array_buffers_.push_back(shared_array_buffer);
188+
msg_->AddSharedArrayBuffer(reference);
189+
return Just(i);
190+
}
191+
142192
void Finish() {
143193
// Only close the MessagePort handles and actually transfer them
144194
// once we know that serialization succeeded.
@@ -166,6 +216,7 @@ class SerializerDelegate : public ValueSerializer::Delegate {
166216
Environment* env_;
167217
Local<Context> context_;
168218
Message* msg_;
219+
std::vector<Local<SharedArrayBuffer>> seen_shared_array_buffers_;
169220
std::vector<MessagePort*> ports_;
170221

171222
friend class worker::Message;
Collapse file

‎src/node_messaging.h‎

Copy file name to clipboardExpand all lines: src/node_messaging.h
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
#include "env.h"
77
#include "node_mutex.h"
8+
#include "sharedarraybuffer_metadata.h"
89
#include <list>
9-
#include <memory>
1010

1111
namespace node {
1212
namespace worker {
@@ -37,13 +37,17 @@ class Message {
3737
v8::Local<v8::Value> input,
3838
v8::Local<v8::Value> transfer_list);
3939

40+
// Internal method of Message that is called when a new SharedArrayBuffer
41+
// object is encountered in the incoming value's structure.
42+
void AddSharedArrayBuffer(SharedArrayBufferMetadataReference ref);
4043
// Internal method of Message that is called once serialization finishes
4144
// and that transfers ownership of `data` to this message.
4245
void AddMessagePort(std::unique_ptr<MessagePortData>&& data);
4346

4447
private:
4548
MallocedBuffer<char> main_message_buf_;
4649
std::vector<MallocedBuffer<char>> array_buffer_contents_;
50+
std::vector<SharedArrayBufferMetadataReference> shared_array_buffers_;
4751
std::vector<std::unique_ptr<MessagePortData>> message_ports_;
4852

4953
friend class MessagePort;
Collapse file

‎src/sharedarraybuffer_metadata.cc‎

Copy file name to clipboard
+129Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#include "sharedarraybuffer_metadata.h"
2+
#include "base_object.h"
3+
#include "base_object-inl.h"
4+
#include "node_errors.h"
5+
6+
using v8::Context;
7+
using v8::Function;
8+
using v8::FunctionTemplate;
9+
using v8::Local;
10+
using v8::Maybe;
11+
using v8::MaybeLocal;
12+
using v8::Nothing;
13+
using v8::Object;
14+
using v8::SharedArrayBuffer;
15+
using v8::Value;
16+
17+
namespace node {
18+
namespace worker {
19+
20+
namespace {
21+
22+
// Yield a JS constructor for SABLifetimePartner objects in the form of a
23+
// standard API object, that has a single field for containing the raw
24+
// SABLiftimePartner* pointer.
25+
Local<Function> GetSABLifetimePartnerConstructor(
26+
Environment* env, Local<Context> context) {
27+
Local<FunctionTemplate> templ;
28+
templ = env->sab_lifetimepartner_constructor_template();
29+
if (!templ.IsEmpty())
30+
return templ->GetFunction(context).ToLocalChecked();
31+
32+
templ = BaseObject::MakeLazilyInitializedJSTemplate(env);
33+
templ->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(),
34+
"SABLifetimePartner"));
35+
env->set_sab_lifetimepartner_constructor_template(templ);
36+
37+
return GetSABLifetimePartnerConstructor(env, context);
38+
}
39+
40+
class SABLifetimePartner : public BaseObject {
41+
public:
42+
SABLifetimePartner(Environment* env,
43+
Local<Object> obj,
44+
SharedArrayBufferMetadataReference r)
45+
: BaseObject(env, obj),
46+
reference(r) {
47+
MakeWeak();
48+
}
49+
50+
SharedArrayBufferMetadataReference reference;
51+
};
52+
53+
} // anonymous namespace
54+
55+
SharedArrayBufferMetadataReference
56+
SharedArrayBufferMetadata::ForSharedArrayBuffer(
57+
Environment* env,
58+
Local<Context> context,
59+
Local<SharedArrayBuffer> source) {
60+
Local<Value> lifetime_partner;
61+
62+
if (!source->GetPrivate(context,
63+
env->sab_lifetimepartner_symbol())
64+
.ToLocal(&lifetime_partner)) {
65+
return nullptr;
66+
}
67+
68+
if (lifetime_partner->IsObject() &&
69+
env->sab_lifetimepartner_constructor_template()
70+
->HasInstance(lifetime_partner)) {
71+
CHECK(source->IsExternal());
72+
SABLifetimePartner* partner =
73+
Unwrap<SABLifetimePartner>(lifetime_partner.As<Object>());
74+
CHECK_NE(partner, nullptr);
75+
return partner->reference;
76+
}
77+
78+
if (source->IsExternal()) {
79+
// If this is an external SharedArrayBuffer but we do not see a lifetime
80+
// partner object, it was not us who externalized it. In that case, there
81+
// is no way to serialize it, because it's unclear how the memory
82+
// is actually owned.
83+
THROW_ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER(env);
84+
return nullptr;
85+
}
86+
87+
SharedArrayBuffer::Contents contents = source->Externalize();
88+
SharedArrayBufferMetadataReference r(new SharedArrayBufferMetadata(
89+
contents.Data(), contents.ByteLength()));
90+
if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
91+
return nullptr;
92+
return r;
93+
}
94+
95+
Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
96+
Environment* env, Local<Context> context,
97+
Local<SharedArrayBuffer> target) {
98+
CHECK(target->IsExternal());
99+
Local<Function> ctor = GetSABLifetimePartnerConstructor(env, context);
100+
Local<Object> obj;
101+
if (!ctor->NewInstance(context).ToLocal(&obj))
102+
return Nothing<bool>();
103+
104+
new SABLifetimePartner(env, obj, shared_from_this());
105+
return target->SetPrivate(context,
106+
env->sab_lifetimepartner_symbol(),
107+
obj);
108+
}
109+
110+
SharedArrayBufferMetadata::SharedArrayBufferMetadata(void* data, size_t size)
111+
: data(data), size(size) { }
112+
113+
SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
114+
free(data);
115+
}
116+
117+
MaybeLocal<SharedArrayBuffer> SharedArrayBufferMetadata::GetSharedArrayBuffer(
118+
Environment* env, Local<Context> context) {
119+
Local<SharedArrayBuffer> obj =
120+
SharedArrayBuffer::New(env->isolate(), data, size);
121+
122+
if (AssignToSharedArrayBuffer(env, context, obj).IsNothing())
123+
return MaybeLocal<SharedArrayBuffer>();
124+
125+
return obj;
126+
}
127+
128+
} // namespace worker
129+
} // namespace node

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.