Skip to content

Commit 14d4ad0

Browse files
committed
worker: fix crash when SharedArrayBuffer outlives creating thread
Use the parent thread’s `ArrayBuffer::Allocator` when creating a Worker instance, as that allocator is guaranteed to outlive the Worker itself. This requires making the zero-fill flag a thread_local variable in order to avoid race conditions between different threads. A test for that behaviour is added as well. Fixes: #28777 Fixes: #28773
1 parent 68c83f9 commit 14d4ad0

6 files changed

Lines changed: 71 additions & 12 deletions

File tree

src/api/environment.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ static MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
7676
return result;
7777
}
7878

79+
thread_local uint32_t NodeArrayBufferAllocator::zero_fill_field_ = 1;
80+
7981
void* NodeArrayBufferAllocator::Allocate(size_t size) {
8082
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
8183
return UncheckedCalloc(size);

src/env.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,7 @@ void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const {
930930
void AsyncHooks::grow_async_ids_stack() {
931931
async_ids_stack_.reserve(async_ids_stack_.Length() * 3);
932932

933+
CHECK(!env()->async_hooks_binding().IsEmpty());
933934
env()->async_hooks_binding()->Set(
934935
env()->context(),
935936
env()->async_ids_stack_string(),

src/node_internals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
121121
NodeArrayBufferAllocator* GetImpl() final { return this; }
122122

123123
private:
124-
uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
124+
// Boolean but exposed as uint32 to JS land.
125+
static thread_local uint32_t zero_fill_field_;
125126
};
126127

127128
class DebuggingArrayBufferAllocator final : public NodeArrayBufferAllocator {

src/node_worker.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
using node::options_parser::kDisallowedInEnvironment;
2020
using v8::Array;
21-
using v8::ArrayBuffer;
2221
using v8::Boolean;
2322
using v8::Context;
2423
using v8::Function;
@@ -107,12 +106,13 @@ bool Worker::is_stopped() const {
107106
// (Eventually, the Environment instance should probably also be moved here.)
108107
class WorkerThreadData {
109108
public:
110-
explicit WorkerThreadData(Worker* w)
111-
: w_(w),
112-
array_buffer_allocator_(ArrayBufferAllocator::Create()) {
113-
CHECK_EQ(uv_loop_init(&loop_), 0);
109+
explicit WorkerThreadData(Worker* w) : w_(w) {
110+
IsolateData* parent_isolate_data = w->env()->isolate_data();
114111

115-
Isolate* isolate = NewIsolate(array_buffer_allocator_.get(), &loop_);
112+
CHECK_EQ(uv_loop_init(&loop_), 0);
113+
Isolate::CreateParams params;
114+
params.array_buffer_allocator = parent_isolate_data->allocator();
115+
Isolate* isolate = NewIsolate(&params, &loop_, w->platform_);
116116
CHECK_NOT_NULL(isolate);
117117

118118
{
@@ -121,10 +121,11 @@ class WorkerThreadData {
121121
isolate->SetStackLimit(w_->stack_base_);
122122

123123
HandleScope handle_scope(isolate);
124-
isolate_data_.reset(CreateIsolateData(isolate,
125-
&loop_,
126-
w_->platform_,
127-
array_buffer_allocator_.get()));
124+
isolate_data_.reset(CreateIsolateData(
125+
isolate,
126+
&loop_,
127+
w_->platform_,
128+
parent_isolate_data->node_allocator()));
128129
CHECK(isolate_data_);
129130
if (w_->per_isolate_opts_)
130131
isolate_data_->set_options(std::move(w_->per_isolate_opts_));
@@ -166,7 +167,6 @@ class WorkerThreadData {
166167
private:
167168
Worker* const w_;
168169
uv_loop_t loop_;
169-
std::unique_ptr<ArrayBufferAllocator> array_buffer_allocator_;
170170
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data_;
171171

172172
friend class Worker;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { Worker } = require('worker_threads');
5+
6+
// Make sure that allocating uninitialized ArrayBuffers in one thread does not
7+
// affect the zero-initialization in other threads.
8+
9+
const w = new Worker(`
10+
const { parentPort } = require('worker_threads');
11+
12+
function post() {
13+
const uint32array = new Uint32Array(64);
14+
parentPort.postMessage(uint32array.reduce((a, b) => a + b));
15+
}
16+
17+
setInterval(post, 0);
18+
`, { eval: true });
19+
20+
function allocBuffers() {
21+
Buffer.allocUnsafe(32 * 1024 * 1024);
22+
}
23+
24+
const interval = setInterval(allocBuffers, 0);
25+
26+
let messages = 0;
27+
w.on('message', (sum) => {
28+
assert.strictEqual(sum, 0);
29+
if (messages++ === 100) {
30+
clearInterval(interval);
31+
w.terminate();
32+
}
33+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { Worker } = require('worker_threads');
5+
6+
// Regression test for https://github.com/nodejs/node/issues/28777
7+
// Make sure that SharedArrayBuffers created in Worker threads are accessible
8+
// after the creating thread ended.
9+
10+
const w = new Worker(`
11+
const { parentPort } = require('worker_threads');
12+
const sharedArrayBuffer = new SharedArrayBuffer(4);
13+
parentPort.postMessage(sharedArrayBuffer);
14+
`, { eval: true });
15+
16+
let sharedArrayBuffer;
17+
w.once('message', common.mustCall((message) => sharedArrayBuffer = message));
18+
w.once('exit', common.mustCall(() => {
19+
const uint8array = new Uint8Array(sharedArrayBuffer);
20+
uint8array[0] = 42;
21+
assert.deepStrictEqual(uint8array, new Uint8Array([42, 0, 0, 0]));
22+
}));

0 commit comments

Comments
 (0)