worker: use engine-provided deleter for SharedArrayBuffers

Store the full information we have on a given `SharedArrayBuffer`,
and use the deleter provided by the JS engine to free the memory
when that is needed.

This fixes memory lifetime management for WASM buffers that are
passed through a `MessageChannel` (e.g. between threads).

PR-URL: https://github.com/nodejs/node/pull/25307
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
Anna Henningsen 2019-01-01 21:42:43 +01:00
parent 7e15f0490a
commit 1f1a373df0
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
5 changed files with 62 additions and 9 deletions

View File

@ -89,8 +89,7 @@ SharedArrayBufferMetadata::ForSharedArrayBuffer(
}
SharedArrayBuffer::Contents contents = source->Externalize();
SharedArrayBufferMetadataReference r(new SharedArrayBufferMetadata(
contents.Data(), contents.ByteLength()));
SharedArrayBufferMetadataReference r(new SharedArrayBufferMetadata(contents));
if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
return nullptr;
return r;
@ -111,17 +110,22 @@ Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
obj);
}
SharedArrayBufferMetadata::SharedArrayBufferMetadata(void* data, size_t size)
: data(data), size(size) { }
SharedArrayBufferMetadata::SharedArrayBufferMetadata(
const SharedArrayBuffer::Contents& contents)
: contents_(contents) { }
SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
free(data);
contents_.Deleter()(contents_.Data(),
contents_.ByteLength(),
contents_.DeleterData());
}
MaybeLocal<SharedArrayBuffer> SharedArrayBufferMetadata::GetSharedArrayBuffer(
Environment* env, Local<Context> context) {
Local<SharedArrayBuffer> obj =
SharedArrayBuffer::New(env->isolate(), data, size);
SharedArrayBuffer::New(env->isolate(),
contents_.Data(),
contents_.ByteLength());
if (AssignToSharedArrayBuffer(env, context, obj).IsNothing())
return MaybeLocal<SharedArrayBuffer>();

View File

@ -46,7 +46,7 @@ class SharedArrayBufferMetadata
SharedArrayBufferMetadata(const SharedArrayBufferMetadata&) = delete;
private:
explicit SharedArrayBufferMetadata(void* data, size_t size);
explicit SharedArrayBufferMetadata(const v8::SharedArrayBuffer::Contents&);
// Attach a lifetime tracker object with a reference count to `target`.
v8::Maybe<bool> AssignToSharedArrayBuffer(
@ -54,8 +54,7 @@ class SharedArrayBufferMetadata
v8::Local<v8::Context> context,
v8::Local<v8::SharedArrayBuffer> target);
void* data = nullptr;
size_t size = 0;
v8::SharedArrayBuffer::Contents contents_;
};
} // namespace worker

Binary file not shown.

View File

@ -0,0 +1,4 @@
(module
(memory $mem 1 2 shared)
(export "memory" (memory $mem))
)

View File

@ -0,0 +1,46 @@
// Flags: --experimental-worker --experimental-wasm-threads
'use strict';
const common = require('../common');
const assert = require('assert');
const { MessageChannel, Worker } = require('worker_threads');
// Test that SharedArrayBuffer instances created from WASM are transferrable
// through MessageChannels (without crashing).
const fixtures = require('../common/fixtures');
const wasmSource = fixtures.readSync('wasm-threads-shared-memory.wasm');
const wasmModule = new WebAssembly.Module(wasmSource);
const instance = new WebAssembly.Instance(wasmModule);
const { buffer } = instance.exports.memory;
assert(buffer instanceof SharedArrayBuffer);
{
const { port1, port2 } = new MessageChannel();
port1.postMessage(buffer);
port2.once('message', common.mustCall((buffer2) => {
// Make sure serialized + deserialized buffer refer to the same memory.
const expected = 'Hello, world!';
const bytes = Buffer.from(buffer).write(expected);
const deserialized = Buffer.from(buffer2).toString('utf8', 0, bytes);
assert.deepStrictEqual(deserialized, expected);
}));
}
{
// Make sure we can free WASM memory originating from a thread that already
// stopped when we exit.
const worker = new Worker(`
const { parentPort } = require('worker_threads');
const wasmSource = new Uint8Array([${wasmSource.join(',')}]);
const wasmModule = new WebAssembly.Module(wasmSource);
const instance = new WebAssembly.Instance(wasmModule);
parentPort.postMessage(instance.exports.memory);
`, { eval: true });
worker.once('message', common.mustCall(({ buffer }) => {
assert(buffer instanceof SharedArrayBuffer);
worker.buf = buffer; // Basically just keep the reference to buffer alive.
}));
worker.once('exit', common.mustCall());
worker.postMessage({ wasmModule });
}