8000 src: add internal binding for constructing SharedArrayBuffers by Renegade334 · Pull Request #60497 · nodejs/node · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions lib/eslint.config_partial.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@ export default [
message: 'Use `const { ShadowRealm } = globalThis;` instead of the global.',
},
// SharedArrayBuffer is not available in primordials because it can be
// disabled with --no-harmony-sharedarraybuffer CLI flag.
// disabled with --enable-sharedarraybuffer-per-context CLI flag.
{
name: 'SharedArrayBuffer',
message: 'Use `const { SharedArrayBuffer } = globalThis;` instead of the global.',
message: "Use `const { constructSharedArrayBuffer } = require('internal/util');` instead of the global.",
},
{
name: 'TextDecoder',
Expand Down
33 changes: 14 additions & 19 deletions lib/internal/main/worker_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ const {
ObjectDefineProperty,
PromisePrototypeThen,
RegExpPrototypeExec,
globalThis: {
SharedArrayBuffer,
},
} = primordials;

const {
Expand Down Expand Up @@ -119,23 +116,21 @@ port.on('message', (message) => {
require('internal/worker').assignEnvironmentData(environmentData);
setupMainThreadPort(mainThreadPort);

if (SharedArrayBuffer !== undefined) {
// The counter is only passed to the workers created by the main thread,
// not to workers created by other workers.
let cachedCwd = '';
let lastCounter = -1;
const originalCwd = process.cwd;

process.cwd = function() {
const currentCounter = AtomicsLoad(cwdCounter, 0);
if (currentCounter === lastCounter)
return cachedCwd;
lastCounter = currentCounter;
cachedCwd = originalCwd();
// The counter is only passed to the workers created by the main thread,
// not to workers created by other workers.
let cachedCwd = '';
let lastCounter = -1;
const originalCwd = process.cwd;

process.cwd = function() {
const currentCounter = AtomicsLoad(cwdCounter, 0);
if (currentCounter === lastCounter)
return cachedCwd;
};
workerIo.sharedCwdCounter = cwdCounter;
}
lastCounter = currentCounter;
cachedCwd = originalCwd();
return cachedCwd;
};
workerIo.sharedCwdCounter = cwdCounter;

const isLoaderHookWorker = (filename === 'internal/modules/esm/worker' && doEval === 'internal');
if (!isLoaderHookWorker) {
Expand Down
8 changes: 2 additions & 6 deletions lib/internal/modules/esm/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,8 @@ const {
SafeSet,
StringPrototypeSlice,
StringPrototypeToUpperCase,
globalThis,
} = primordials;

const {
SharedArrayBuffer,
} = globalThis;

const {
ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED,
ERR_INTERNAL_ASSERTION,
Expand All @@ -44,6 +39,7 @@ const {
validateString,
} = require('internal/validators');
const {
constructSharedArrayBuffer,
kEmptyObject,
} = require('internal/util');

Expand Down Expand Up @@ -535,7 +531,7 @@ class AsyncLoaderHookWorker {
const { InternalWorker } = require('internal/worker');
MessageChannel ??= require('internal/worker/io').MessageChannel;

const lock = new SharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH);
const lock = constructSharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH);
this.#lock = new Int32Array(lock);

this.#worker = new InternalWorker('internal/modules/esm/worker', {
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/streams/fast-utf8-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ const {
AtomicsWait,
Int32Array,
MathMax,
Number,
SymbolDispose,
globalThis: {
Number,
SharedArrayBuffer,
},
} = primordials;

const {
constructSharedArrayBuffer,
} = require('internal/util');

const {
Buffer,
} = require('buffer');
Expand Down Expand Up @@ -49,7 +50,7 @@ const {
const BUSY_WRITE_TIMEOUT = 100;
const kEmptyBuffer = Buffer.allocUnsafe(0);

const kNil = new Int32Array(new SharedArrayBuffer(4));
const kNil = new Int32Array(constructSharedArrayBuffer(4));

function sleep(ms) {
// Also filters out NaN, non-number types, including empty strings, but allows bigints
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const {
} = require('internal/errors');
const { signals } = internalBinding('constants').os;
const {
constructSharedArrayBuffer,
guessHandleType: _guessHandleType,
defineLazyProperties,
privateSymbols: {
Expand Down Expand Up @@ -954,6 +955,7 @@ module.exports = {
assertTypeScript,
assignFunctionName,
cachedResult,
constructSharedArrayBuffer,
convertToValidSignal,
createClassWrapper,
decorateErrorStack,
Expand Down
11 changes: 6 additions & 5 deletions lib/internal/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const {
SymbolFor,
TypedArrayPrototypeFill,
Uint32Array,
globalThis: { SharedArrayBuffer },
} = primordials;

const EventEmitter = require('events');
Expand Down Expand Up @@ -62,7 +61,10 @@ const {
const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker/messaging');
const { deserializeError } = require('internal/error_serdes');
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
const { kEmptyObject } = require('internal/util');
const {
constructSharedArrayBuffer,
kEmptyObject,
} = require('internal/util');
const { validateArray, validateString, validateObject, validateNumber } = require('internal/validators');
const {
throwIfBuildingSnapshot,
Expand Down Expand Up @@ -106,9 +108,8 @@ let cwdCounter;

const environmentData = new SafeMap();

// SharedArrayBuffers can be disabled with --enable-sharedarraybuffer-per-context.
if (isMainThread && SharedArrayBuffer !== undefined) {
cwdCounter = new Uint32Array(new SharedArrayBuffer(4));
if (isMainThread) {
cwdCounter = new Uint32Array(constructSharedArrayBuffer(4));
const originalChdir = process.chdir;
process.chdir = function(path) {
AtomicsAdd(cwdCounter, 0, 1);
Expand Down
11 changes: 5 additions & 6 deletions lib/internal/worker/messaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ const {
AtomicsWaitAsync,
Int32Array,
SafeMap,
globalThis,
} = primordials;

const {
SharedArrayBuffer,
} = globalThis;

const {
isMainThread,
threadId: currentThreadId,
} = internalBinding('worker');

const {
constructSharedArrayBuffer,
} = require('internal/util');

const {
codes: {
ERR_WORKER_MESSAGING_ERRORED,
Expand Down Expand Up @@ -203,7 +202,7 @@ async function postMessageToThread(threadId, value, transferList, timeout) {
throw new ERR_WORKER_MESSAGING_SAME_THREAD();
}

const memory = new SharedArrayBuffer(WORKER_MESSAGING_SHARED_DATA);
const memory = constructSharedArrayBuffer(WORKER_MESSAGING_SHARED_DATA);
const status = new Int32Array(memory);
const promise = AtomicsWaitAsync(status, WORKER_MESSAGING_STATUS_INDEX, 0, timeout).value;

Expand Down
32 changes: 31 additions & 1 deletion src/node_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace util {

using v8::ALL_PROPERTIES;
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BigInt;
using v8::Boolean;
Expand All @@ -34,6 +35,7 @@ using v8::ONLY_WRITABLE;
using v8::Promise;
using v8::PropertyFilter;
using v8::Proxy;
using v8::SharedArrayBuffer;
using v8::SKIP_STRINGS;
using v8::SKIP_SYMBOLS;
using v8::StackFrame;
Expand Down Expand Up @@ -438,6 +440,30 @@ static void DefineLazyProperties(const FunctionCallbackInfo<Value>& args) {
}
}

void ConstructSharedArrayBuffer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
int64_t length;
// Note: IntegerValue() clamps its output, so excessively large input values
// will not overflow
if (!args[0]->IntegerValue(env->context()).To(&length)) {
return;
}
if (length < 0 ||
static_cast<uint64_t>(length) > ArrayBuffer::kMaxByteLength) {
env->ThrowRangeError("Invalid array buffer length");
return;
}
Local<SharedArrayBuffer> sab;
if (!SharedArrayBuffer::MaybeNew(env->isolate(), static_cast<size_t>(length))
.ToLocal(&sab)) {
// Note: SharedArrayBuffer::MaybeNew doesn't schedule an exception if it
// fails
env->ThrowRangeError("Array buffer allocation failed");
return;
}
args.GetReturnValue().Set(sab);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GetPromiseDetails);
registry->Register(GetProxyDetails);
Expand All @@ -455,6 +481,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(IsInsideNodeModules);
registry->Register(DefineLazyProperties);
registry->Register(DefineLazyPropertiesGetter);
registry->Register(ConstructSharedArrayBuffer);
}

void Initialize(Local<Object> target,
Expand Down Expand Up @@ -554,9 +581,12 @@ void Initialize(Local<Object> target,
SetMethodNoSideEffect(context, target, "getCallSites", GetCallSites);
SetMethod(context, target, "sleep", Sleep);
SetMethod(context, target, "parseEnv", ParseEnv);

SetMethod(
context, target, "arrayBufferViewHasBuffer", ArrayBufferViewHasBuffer);
SetMethod(context,
target,
"constructSharedArrayBuffer",
ConstructSharedArrayBuffer);

Local<String> should_abort_on_uncaught_toggle =
FIXED_ONE_BYTE_STRING(env->isolate(), "shouldAbortOnUncaughtToggle");
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-internal-util-construct-sab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Flags: --enable-sharedarraybuffer-per-context --expose-internals
'use strict';

require('../common');
const assert = require('assert');
const { isSharedArrayBuffer } = require('util/types');
const { constructSharedArrayBuffer } = require('internal/util');

// We're testing that we can construct a SAB even when the global is not exposed.
assert.strictEqual(typeof SharedArrayBuffer, 'undefined');

for (const length of [undefined, 0, 1, 2 ** 32]) {
assert(isSharedArrayBuffer(constructSharedArrayBuffer(length)));
}

for (const length of [-1, Number.MAX_SAFE_INTEGER + 1, 2 ** 64]) {
assert.throws(() => constructSharedArrayBuffer(length), RangeError);
}
1 change: 1 addition & 0 deletions typings/internalBinding/util.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface UtilBinding {
parseEnv(content: string): Record<string, string>;
styleText(format: Array<string> | string, text: string): string;
isInsideNodeModules(frameLimit: number, defaultValue: unknown): boolean;
constructSharedArrayBuffer(length?: number): SharedArrayBuffer;

constants: {
kPending: 0;
Expand Down
Loading
0