worker: fix crash when SharedArrayBuffer outlives creating thread

Keep a reference to the `ArrayBuffer::Allocator` alive for at least
as long as a `SharedArrayBuffer` allocated by it lives.

Refs: https://github.com/nodejs/node/pull/28788
Fixes: https://github.com/nodejs/node/issues/28777
Fixes: https://github.com/nodejs/node/issues/28773

PR-URL: https://github.com/nodejs/node/pull/29190
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
Anna Henningsen 2019-08-18 00:05:48 +02:00
parent bdf07f4317
commit 119c4ccf0e
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
6 changed files with 83 additions and 9 deletions

View File

@ -59,6 +59,7 @@ Worker::Worker(Environment* env,
per_isolate_opts_(per_isolate_opts), per_isolate_opts_(per_isolate_opts),
exec_argv_(exec_argv), exec_argv_(exec_argv),
platform_(env->isolate_data()->platform()), platform_(env->isolate_data()->platform()),
array_buffer_allocator_(ArrayBufferAllocator::Create()),
start_profiler_idle_notifier_(env->profiler_idle_notifier_started()), start_profiler_idle_notifier_(env->profiler_idle_notifier_started()),
thread_id_(Environment::AllocateThreadId()), thread_id_(Environment::AllocateThreadId()),
env_vars_(env->env_vars()) { env_vars_(env->env_vars()) {
@ -102,17 +103,20 @@ bool Worker::is_stopped() const {
return stopped_; return stopped_;
} }
std::shared_ptr<ArrayBufferAllocator> Worker::array_buffer_allocator() {
return array_buffer_allocator_;
}
// This class contains data that is only relevant to the child thread itself, // This class contains data that is only relevant to the child thread itself,
// and only while it is running. // and only while it is running.
// (Eventually, the Environment instance should probably also be moved here.) // (Eventually, the Environment instance should probably also be moved here.)
class WorkerThreadData { class WorkerThreadData {
public: public:
explicit WorkerThreadData(Worker* w) explicit WorkerThreadData(Worker* w)
: w_(w), : w_(w) {
array_buffer_allocator_(ArrayBufferAllocator::Create()) {
CHECK_EQ(uv_loop_init(&loop_), 0); CHECK_EQ(uv_loop_init(&loop_), 0);
Isolate* isolate = NewIsolate(array_buffer_allocator_.get(), &loop_); Isolate* isolate = NewIsolate(w->array_buffer_allocator_.get(), &loop_);
CHECK_NOT_NULL(isolate); CHECK_NOT_NULL(isolate);
{ {
@ -124,7 +128,7 @@ class WorkerThreadData {
isolate_data_.reset(CreateIsolateData(isolate, isolate_data_.reset(CreateIsolateData(isolate,
&loop_, &loop_,
w_->platform_, w_->platform_,
array_buffer_allocator_.get())); w->array_buffer_allocator_.get()));
CHECK(isolate_data_); CHECK(isolate_data_);
if (w_->per_isolate_opts_) if (w_->per_isolate_opts_)
isolate_data_->set_options(std::move(w_->per_isolate_opts_)); isolate_data_->set_options(std::move(w_->per_isolate_opts_));
@ -166,7 +170,6 @@ class WorkerThreadData {
private: private:
Worker* const w_; Worker* const w_;
uv_loop_t loop_; uv_loop_t loop_;
std::unique_ptr<ArrayBufferAllocator> array_buffer_allocator_;
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_; DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_;
friend class Worker; friend class Worker;

View File

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

View File

@ -1,7 +1,9 @@
#include "sharedarraybuffer_metadata.h" #include "sharedarraybuffer_metadata.h"
#include "base_object-inl.h" #include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h" #include "node_errors.h"
#include "node_worker.h"
#include "util-inl.h" #include "util-inl.h"
#include <utility> #include <utility>
@ -91,8 +93,16 @@ SharedArrayBufferMetadata::ForSharedArrayBuffer(
return nullptr; 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(); SharedArrayBuffer::Contents contents = source->Externalize();
SharedArrayBufferMetadataReference r(new SharedArrayBufferMetadata(contents)); SharedArrayBufferMetadataReference r(
new SharedArrayBufferMetadata(contents, allocator));
if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing()) if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
return nullptr; return nullptr;
return r; return r;
@ -114,8 +124,9 @@ Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
} }
SharedArrayBufferMetadata::SharedArrayBufferMetadata( SharedArrayBufferMetadata::SharedArrayBufferMetadata(
const SharedArrayBuffer::Contents& contents) const SharedArrayBuffer::Contents& contents,
: contents_(contents) { } std::shared_ptr<v8::ArrayBuffer::Allocator> allocator)
: contents_(contents), allocator_(allocator) { }
SharedArrayBufferMetadata::~SharedArrayBufferMetadata() { SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
contents_.Deleter()(contents_.Data(), contents_.Deleter()(contents_.Data(),

View File

@ -46,7 +46,9 @@ class SharedArrayBufferMetadata
SharedArrayBufferMetadata(const SharedArrayBufferMetadata&) = delete; SharedArrayBufferMetadata(const SharedArrayBufferMetadata&) = delete;
private: private:
explicit SharedArrayBufferMetadata(const v8::SharedArrayBuffer::Contents&); SharedArrayBufferMetadata(
const v8::SharedArrayBuffer::Contents&,
std::shared_ptr<v8::ArrayBuffer::Allocator>);
// Attach a lifetime tracker object with a reference count to `target`. // Attach a lifetime tracker object with a reference count to `target`.
v8::Maybe<bool> AssignToSharedArrayBuffer( v8::Maybe<bool> AssignToSharedArrayBuffer(
@ -55,6 +57,7 @@ class SharedArrayBufferMetadata
v8::Local<v8::SharedArrayBuffer> target); v8::Local<v8::SharedArrayBuffer> target);
v8::SharedArrayBuffer::Contents contents_; v8::SharedArrayBuffer::Contents contents_;
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator_;
}; };
} // namespace worker } // namespace worker

View File

@ -0,0 +1,33 @@
'use strict';
require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');
// Make sure that allocating uninitialized ArrayBuffers in one thread does not
// affect the zero-initialization in other threads.
const w = new Worker(`
const { parentPort } = require('worker_threads');
function post() {
const uint32array = new Uint32Array(64);
parentPort.postMessage(uint32array.reduce((a, b) => a + b));
}
setInterval(post, 0);
`, { eval: true });
function allocBuffers() {
Buffer.allocUnsafe(32 * 1024 * 1024);
}
const interval = setInterval(allocBuffers, 0);
let messages = 0;
w.on('message', (sum) => {
assert.strictEqual(sum, 0);
if (messages++ === 100) {
clearInterval(interval);
w.terminate();
}
});

View File

@ -0,0 +1,22 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { Worker } = require('worker_threads');
// Regression test for https://github.com/nodejs/node/issues/28777
// Make sure that SharedArrayBuffers created in Worker threads are accessible
// after the creating thread ended.
const w = new Worker(`
const { parentPort } = require('worker_threads');
const sharedArrayBuffer = new SharedArrayBuffer(4);
parentPort.postMessage(sharedArrayBuffer);
`, { eval: true });
let sharedArrayBuffer;
w.once('message', common.mustCall((message) => sharedArrayBuffer = message));
w.once('exit', common.mustCall(() => {
const uint8array = new Uint8Array(sharedArrayBuffer);
uint8array[0] = 42;
assert.deepStrictEqual(uint8array, new Uint8Array([42, 0, 0, 0]));
}));