Skip to content
Permalink
Browse files

worker: use engine-provided deleter for `SharedArrayBuffer`s

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: #25307
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
  • Loading branch information...
addaleax committed Jan 1, 2019
1 parent e14f864 commit 2014eba782074752e58847c46e6d21d46ef8a8e1
@@ -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>();
@@ -46,16 +46,15 @@ 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(
Environment* env,
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.
@@ -0,0 +1,4 @@
(module
(memory $mem 1 2 shared)
(export "memory" (memory $mem))
)
@@ -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 });
}

0 comments on commit 2014eba

Please sign in to comment.
You can’t perform that action at this time.