fix(ext/node): enable Buffer pool for strings (#29592)

Part 1 towards enabling `parallel/test-buffer-pool-untransferable.js`
This commit is contained in:
Divy Srivastava 2025-06-04 20:04:32 -07:00 committed by GitHub
parent 1e6aca57e8
commit 8b81644b59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 83 additions and 24 deletions

View file

@ -550,7 +550,7 @@ Object.defineProperties(
if (data instanceof Buffer) { if (data instanceof Buffer) {
data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
} }
if (data.buffer.byteLength > 0) { if (data.byteLength > 0) {
this._bodyWriter.ready.then(() => { this._bodyWriter.ready.then(() => {
if (this._bodyWriter.desiredSize > 0) { if (this._bodyWriter.desiredSize > 0) {
this._bodyWriter.write(data).then(() => { this._bodyWriter.write(data).then(() => {

View file

@ -207,9 +207,8 @@ function createBuffer(length) {
'The value "' + length + '" is invalid for option "size"', 'The value "' + length + '" is invalid for option "size"',
); );
} }
const buf = new Uint8Array(length);
ObjectSetPrototypeOf(buf, BufferPrototype); return new FastBuffer(length);
return buf;
} }
/** /**
@ -238,7 +237,24 @@ export function Buffer(arg, encodingOrOffset, length) {
return _from(arg, encodingOrOffset, length); return _from(arg, encodingOrOffset, length);
} }
Buffer.poolSize = 8192; Buffer.poolSize = 8 * 1024;
let poolSize, poolOffset, allocPool, allocBuffer;
function createPool() {
poolSize = Buffer.poolSize;
allocBuffer = new Uint8Array(poolSize);
allocPool = TypedArrayPrototypeGetBuffer(allocBuffer);
poolOffset = 0;
}
createPool();
function alignPool() {
// Ensure aligned slices
if (poolOffset & 0x7) {
poolOffset |= 0x7;
poolOffset++;
}
}
function _from(value, encodingOrOffset, length) { function _from(value, encodingOrOffset, length) {
if (typeof value === "string") { if (typeof value === "string") {
@ -396,20 +412,36 @@ function fromString(string, encoding) {
if (!BufferIsEncoding(encoding)) { if (!BufferIsEncoding(encoding)) {
throw new codes.ERR_UNKNOWN_ENCODING(encoding); throw new codes.ERR_UNKNOWN_ENCODING(encoding);
} }
const maxLength = Buffer.poolSize >>> 1;
const length = byteLength(string, encoding) | 0; const length = byteLength(string, encoding) | 0;
let buf = createBuffer(length); if (length >= maxLength) {
const actual = buf.write(string, encoding); let buf = createBuffer(length);
if (actual !== length) { const actual = buf.write(string, encoding);
// deno-lint-ignore prefer-primordials if (actual !== length) {
buf = buf.slice(0, actual); // deno-lint-ignore prefer-primordials
buf = buf.slice(0, actual);
}
return buf;
} }
return buf;
if (length > (poolSize - poolOffset)) {
createPool();
}
const ops = getEncodingOps(encoding);
let b = new FastBuffer(allocPool, poolOffset, length);
const actual = ops.write(b, string, 0, length);
if (actual !== length) {
// byteLength() may overestimate the length, so we slice it down.
b = new FastBuffer(allocPool, poolOffset, actual);
}
poolOffset += actual;
alignPool();
return b;
} }
function fromArrayLike(obj) { function fromArrayLike(obj) {
const buf = new Uint8Array(obj); return new FastBuffer(obj);
ObjectSetPrototypeOf(buf, BufferPrototype);
return buf;
} }
function fromObject(obj) { function fromObject(obj) {
@ -447,7 +479,7 @@ ObjectSetPrototypeOf(SlowBuffer.prototype, Uint8ArrayPrototype);
ObjectSetPrototypeOf(SlowBuffer, Uint8Array); ObjectSetPrototypeOf(SlowBuffer, Uint8Array);
const BufferIsBuffer = Buffer.isBuffer = function isBuffer(b) { const BufferIsBuffer = Buffer.isBuffer = function isBuffer(b) {
return b != null && b._isBuffer === true && b !== BufferPrototype; return ObjectPrototypeIsPrototypeOf(Buffer.prototype, b);
}; };
const BufferCompare = Buffer.compare = function compare(a, b) { const BufferCompare = Buffer.compare = function compare(a, b) {
@ -1072,9 +1104,7 @@ function fromArrayBuffer(obj, byteOffset, length) {
} }
} }
const buffer = new Uint8Array(obj, byteOffset, length); return new FastBuffer(obj, byteOffset, length);
ObjectSetPrototypeOf(buffer, BufferPrototype);
return buffer;
} }
function _base64Slice(buf, start, end) { function _base64Slice(buf, start, end) {

View file

@ -39,10 +39,15 @@ const {
Symbol, Symbol,
MathMin, MathMin,
DataViewPrototypeGetBuffer, DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
String, String,
TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
StringPrototypeToLowerCase, StringPrototypeToLowerCase,
Uint8Array,
} = primordials; } = primordials;
const { isTypedArray } = core; const { isTypedArray } = core;
@ -84,10 +89,19 @@ function normalizeBuffer(buf: Buffer) {
if (isBufferType(buf)) { if (isBufferType(buf)) {
return buf; return buf;
} else { } else {
const isTA = isTypedArray(buf);
return Buffer.from( return Buffer.from(
isTypedArray(buf) new Uint8Array(
? TypedArrayPrototypeGetBuffer(buf) isTA
: DataViewPrototypeGetBuffer(buf), ? TypedArrayPrototypeGetBuffer(buf)
: DataViewPrototypeGetBuffer(buf),
isTA
? TypedArrayPrototypeGetByteOffset(buf)
: DataViewPrototypeGetByteOffset(buf),
isTA
? TypedArrayPrototypeGetByteLength(buf)
: DataViewPrototypeGetByteLength(buf),
),
); );
} }
} }

View file

@ -1,6 +1,7 @@
// Copyright 2018-2025 the Deno authors. MIT license. // Copyright 2018-2025 the Deno authors. MIT license.
import { Buffer } from "node:buffer"; import { Buffer } from "node:buffer";
import { assertEquals, assertThrows } from "@std/assert"; import { assertEquals, assertThrows } from "@std/assert";
import { strictEqual } from "node:assert";
Deno.test({ Deno.test({
name: "[node/buffer] alloc fails if size is not a number", name: "[node/buffer] alloc fails if size is not a number",
@ -651,3 +652,12 @@ Deno.test({
assertEquals([...buf], [0x61, 0x62, 0x63, 0, 0, 0, 0, 0]); assertEquals([...buf], [0x61, 0x62, 0x63, 0, 0, 0, 0, 0]);
}, },
}); });
Deno.test({
name: "[node/buffer] Buffer.from pool",
fn() {
const a = Buffer.from("hello world");
const b = Buffer.from("hello world");
strictEqual(a.buffer, b.buffer);
},
});

View file

@ -1153,8 +1153,9 @@ Deno.test({
assertEquals(stdout.toString(), expected); assertEquals(stdout.toString(), expected);
} }
{ {
const b = Buffer.from(text);
const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], { const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], {
input: new DataView(Buffer.from(text).buffer), input: new DataView(b.buffer, b.byteOffset, b.byteLength),
}); });
assertEquals(stdout.toString(), expected); assertEquals(stdout.toString(), expected);
} }

View file

@ -133,7 +133,9 @@ Deno.test(
Deno.test("should work with dataview", () => { Deno.test("should work with dataview", () => {
const buf = Buffer.from("hello world"); const buf = Buffer.from("hello world");
const compressed = brotliCompressSync(new DataView(buf.buffer)); const compressed = brotliCompressSync(
new DataView(buf.buffer, buf.byteOffset, buf.byteLength),
);
const decompressed = brotliDecompressSync(compressed); const decompressed = brotliDecompressSync(compressed);
assertEquals(decompressed.toString(), "hello world"); assertEquals(decompressed.toString(), "hello world");
}); });
@ -164,7 +166,9 @@ Deno.test(
"zlib compression with dataview", "zlib compression with dataview",
() => { () => {
const buf = Buffer.from("hello world"); const buf = Buffer.from("hello world");
const compressed = gzipSync(new DataView(buf.buffer)); const compressed = gzipSync(
new DataView(buf.buffer, buf.byteOffset, buf.byteLength),
);
const decompressed = unzipSync(compressed); const decompressed = unzipSync(compressed);
assertEquals(decompressed.toString(), "hello world"); assertEquals(decompressed.toString(), "hello world");
}, },