Skip to content

Commit

Permalink
src,lib: make JSTransferables based on private symbols
Browse files Browse the repository at this point in the history
Serializes and transfers platform objects implemented as a JS class
based on private symbols instead of V8 object internal slots. This
avoids the need to alter the prototype chains and mixins to make the
JS class to be transferable.
  • Loading branch information
legendecas committed Jul 5, 2023
1 parent a8a6173 commit b61b4c9
Show file tree
Hide file tree
Showing 23 changed files with 342 additions and 232 deletions.
28 changes: 16 additions & 12 deletions lib/internal/abort_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ const {
const assert = require('internal/assert');

const {
messaging_deserialize_symbol: kDeserialize,
messaging_transfer_symbol: kTransfer,
messaging_transfer_list_symbol: kTransferList,
} = internalBinding('symbols');
kDeserialize,
kTransfer,
kTransferList,
} = require('internal/worker/js_transferable');

let _MessageChannel;
let makeTransferable;
let markTransferMode;

// Loading the MessageChannel and makeTransferable have to be done lazily
// Loading the MessageChannel and markTransferable have to be done lazily
// because otherwise we'll end up with a require cycle that ends up with
// an incomplete initialization of abort_controller.

Expand All @@ -75,10 +75,10 @@ function lazyMessageChannel() {
return new _MessageChannel();
}

function lazyMakeTransferable(obj) {
makeTransferable ??=
require('internal/worker/js_transferable').makeTransferable;
return makeTransferable(obj);
function lazyMarkTransferMode(obj, cloneable, transferable) {
markTransferMode ??=
require('internal/worker/js_transferable').markTransferMode;
markTransferMode(obj, cloneable, transferable);
}

const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
Expand Down Expand Up @@ -355,7 +355,10 @@ function createAbortSignal(init = kEmptyObject) {
signal[kAborted] = aborted;
signal[kReason] = reason;
signal[kComposite] = composite;
return transferable ? lazyMakeTransferable(signal) : signal;
if (transferable) {
lazyMarkTransferMode(signal, false, true);
}
return signal;
}

function abortSignal(signal, reason) {
Expand Down Expand Up @@ -411,7 +414,8 @@ class AbortController {
function transferableAbortSignal(signal) {
if (signal?.[kAborted] === undefined)
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
return lazyMakeTransferable(signal);
lazyMarkTransferMode(signal, false, true);
return signal;
}

/**
Expand Down
16 changes: 9 additions & 7 deletions lib/internal/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const {
const { URL } = require('internal/url');

const {
makeTransferable,
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
Expand Down Expand Up @@ -136,6 +136,8 @@ class Blob {
* @constructs {Blob}
*/
constructor(sources = [], options) {
markTransferMode(this, true, false);

if (sources === null ||
typeof sources[SymbolIterator] !== 'function' ||
typeof sources === 'string') {
Expand Down Expand Up @@ -167,9 +169,6 @@ class Blob {
type = `${type}`;
this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ?
'' : StringPrototypeToLowerCase(type);

// eslint-disable-next-line no-constructor-return
return makeTransferable(this);
}

[kInspect](depth, options) {
Expand Down Expand Up @@ -385,16 +384,19 @@ class Blob {
}

function ClonedBlob() {
return makeTransferable(ReflectConstruct(function() {}, [], Blob));
return ReflectConstruct(function() {
markTransferMode(this, true, false);
}, [], Blob);
}
ClonedBlob.prototype[kDeserialize] = () => {};

function createBlob(handle, length, type = '') {
return makeTransferable(ReflectConstruct(function() {
return ReflectConstruct(function() {
markTransferMode(this, true, false);
this[kHandle] = handle;
this[kType] = type;
this[kLength] = length;
}, [], Blob));
}, [], Blob);
}

ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
Expand Down
10 changes: 5 additions & 5 deletions lib/internal/blocklist.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const {
} = require('internal/socketaddress');

const {
JSTransferable,
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
Expand All @@ -36,9 +36,9 @@ const {

const { validateInt32, validateString } = require('internal/validators');

class BlockList extends JSTransferable {
class BlockList {
constructor() {
super();
markTransferMode(this, true, false);
this[kHandle] = new BlockListHandle();
this[kHandle][owner_symbol] = this;
}
Expand Down Expand Up @@ -148,9 +148,9 @@ class BlockList extends JSTransferable {
}
}

class InternalBlockList extends JSTransferable {
class InternalBlockList {
constructor(handle) {
super();
markTransferMode(this, true, false);
this[kHandle] = handle;
if (handle !== undefined)
handle[owner_symbol] = this;
Expand Down
8 changes: 3 additions & 5 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const {
} = require('internal/util/types');

const {
makeTransferable,
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
Expand Down Expand Up @@ -706,24 +706,22 @@ ObjectDefineProperties(CryptoKey.prototype, {
// All internal code must use new InternalCryptoKey to create
// CryptoKey instances. The CryptoKey class is exposed to end
// user code but is not permitted to be constructed directly.
// Using makeTransferable also allows the CryptoKey to be
// Using markTransferMode also allows the CryptoKey to be
// cloned to Workers.
class InternalCryptoKey {
constructor(
keyObject,
algorithm,
keyUsages,
extractable) {
markTransferMode(this, true, false);
// Using symbol properties here currently instead of private
// properties because (for now) the performance penalty of
// private fields is still too high.
this[kKeyObject] = keyObject;
this[kAlgorithm] = algorithm;
this[kExtractable] = extractable;
this[kKeyUsages] = keyUsages;

// eslint-disable-next-line no-constructor-return
return makeTransferable(this);
}

[kClone]() {
Expand Down
10 changes: 5 additions & 5 deletions lib/internal/crypto/x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const {
} = require('internal/errors');

const {
JSTransferable,
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
Expand Down Expand Up @@ -94,16 +94,16 @@ function getFlags(options = kEmptyObject) {
return flags;
}

class InternalX509Certificate extends JSTransferable {
class InternalX509Certificate {
[kInternalState] = new SafeMap();

constructor(handle) {
super();
markTransferMode(this, true, false);
this[kHandle] = handle;
}
}

class X509Certificate extends JSTransferable {
class X509Certificate {
[kInternalState] = new SafeMap();

constructor(buffer) {
Expand All @@ -115,7 +115,7 @@ class X509Certificate extends JSTransferable {
['string', 'Buffer', 'TypedArray', 'DataView'],
buffer);
}
super();
markTransferMode(this, true, false);
this[kHandle] = parseX509(buffer);
}

Expand Down
25 changes: 0 additions & 25 deletions lib/internal/event_target.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ const {
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptors,
ObjectSetPrototypeOf,
ObjectValues,
ReflectApply,
SafeArrayIterator,
SafeFinalizationRegistry,
SafeMap,
SafeWeakMap,
Expand Down Expand Up @@ -1114,30 +1110,9 @@ function defineEventHandler(emitter, name, event = name) {
});
}

const EventEmitterMixin = (Superclass) => {
class MixedEventEmitter extends Superclass {
constructor(...args) {
args = new SafeArrayIterator(args);
super(...args);
FunctionPrototypeCall(EventEmitter, this);
}
}
const protoProps = ObjectGetOwnPropertyDescriptors(EventEmitter.prototype);
delete protoProps.constructor;
const propertiesValues = ObjectValues(protoProps);
for (let i = 0; i < propertiesValues.length; i++) {
// We want to use null-prototype objects to not rely on globally mutable
// %Object.prototype%.
ObjectSetPrototypeOf(propertiesValues[i], null);
}
ObjectDefineProperties(MixedEventEmitter.prototype, protoProps);
return MixedEventEmitter;
};

module.exports = {
Event,
CustomEvent,
EventEmitterMixin,
EventTarget,
NodeEventTarget,
defineEventHandler,
Expand Down
7 changes: 4 additions & 3 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const {
lazyDOMException,
promisify,
} = require('internal/util');
const { EventEmitterMixin } = require('internal/event_target');
const EventEmitter = require('events');
const { StringDecoder } = require('string_decoder');
const { kFSWatchStart, watch } = require('internal/fs/watchers');
const nonNativeWatcher = require('internal/fs/recursive_watch');
Expand All @@ -110,7 +110,7 @@ const kLocked = Symbol('kLocked');
const { kUsePromises } = binding;
const { Interface } = require('internal/readline/interface');
const {
JSTransferable, kDeserialize, kTransfer, kTransferList,
kDeserialize, kTransfer, kTransferList, markTransferMode,
} = require('internal/worker/js_transferable');

const getDirectoryEntriesPromise = promisify(getDirents);
Expand All @@ -130,12 +130,13 @@ function lazyFsStreams() {
return fsStreams ??= require('internal/fs/streams');
}

class FileHandle extends EventEmitterMixin(JSTransferable) {
class FileHandle extends EventEmitter {
/**
* @param {InternalFSBinding.FileHandle | undefined} filehandle
*/
constructor(filehandle) {
super();
markTransferMode(this, false, true);
this[kHandle] = filehandle;
this[kFd] = filehandle ? filehandle.fd : -1;

Expand Down
12 changes: 7 additions & 5 deletions lib/internal/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const kRecordable = Symbol('kRecordable');
const {
kClone,
kDeserialize,
makeTransferable,
markTransferMode,
} = require('internal/worker/js_transferable');

function isHistogram(object) {
Expand Down Expand Up @@ -319,21 +319,23 @@ class RecordableHistogram extends Histogram {
}

function internalHistogram(handle) {
return makeTransferable(ReflectConstruct(
return ReflectConstruct(
function() {
markTransferMode(this, true, false);
this[kHandle] = handle;
this[kMap] = new SafeMap();
}, [], Histogram));
}, [], Histogram);
}
internalHistogram.prototype[kDeserialize] = () => {};

function internalRecordableHistogram(handle) {
return makeTransferable(ReflectConstruct(
return ReflectConstruct(
function() {
markTransferMode(this, true, false);
this[kHandle] = handle;
this[kMap] = new SafeMap();
this[kRecordable] = true;
}, [], RecordableHistogram));
}, [], RecordableHistogram);
}
internalRecordableHistogram.prototype[kDeserialize] = () => {};

Expand Down
7 changes: 4 additions & 3 deletions lib/internal/perf/event_loop_delay.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const {
} = require('internal/util');

const {
makeTransferable,
markTransferMode,
} = require('internal/worker/js_transferable');

const kEnabled = Symbol('kEnabled');
Expand Down Expand Up @@ -79,12 +79,13 @@ function monitorEventLoopDelay(options = kEmptyObject) {
const { resolution = 10 } = options;
validateInteger(resolution, 'options.resolution', 1);

return makeTransferable(ReflectConstruct(
return ReflectConstruct(
function() {
markTransferMode(this, true, false);
this[kEnabled] = false;
this[kHandle] = createELDHistogram(resolution);
this[kMap] = new SafeMap();
}, [], ELDHistogram));
}, [], ELDHistogram);
}

module.exports = monitorEventLoopDelay;
12 changes: 7 additions & 5 deletions lib/internal/socketaddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,22 @@ const {
const { inspect } = require('internal/util/inspect');

const {
JSTransferable,
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');

const kHandle = Symbol('kHandle');
const kDetail = Symbol('kDetail');

class SocketAddress extends JSTransferable {
class SocketAddress {
static isSocketAddress(value) {
return value?.[kHandle] !== undefined;
}

constructor(options = kEmptyObject) {
super();
markTransferMode(this, true, false);

validateObject(options, 'options');
let { family = 'ipv4' } = options;
const {
Expand Down Expand Up @@ -139,9 +140,10 @@ class SocketAddress extends JSTransferable {
}
}

class InternalSocketAddress extends JSTransferable {
class InternalSocketAddress {
constructor(handle) {
super();
markTransferMode(this, true, false);

this[kHandle] = handle;
this[kDetail] = this[kHandle]?.detail({
address: undefined,
Expand Down
Loading

0 comments on commit b61b4c9

Please sign in to comment.