Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
src,lib: make JSTransferables based on private symbols
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
commit b61b4c91d95b89acb8c2a86b22a9f31213a26031
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