Skip to content

Commit 85e4ef5

Browse files
committed
vfs: use geometric doubling for write buffer expansion
Switch MemoryFileHandle from exact-fit allocation to geometric doubling (capacity * 2) for amortized O(1) append performance. Track actual content size separately from buffer capacity via a #size field, and expose only valid data through subarray views.
1 parent f824310 commit 85e4ef5

1 file changed

Lines changed: 47 additions & 22 deletions

File tree

lib/internal/vfs/file_handle.js

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
DateNow,
5+
MathMax,
56
MathMin,
67
Symbol,
78
} = primordials;
@@ -253,6 +254,7 @@ class VirtualFileHandle {
253254
*/
254255
class MemoryFileHandle extends VirtualFileHandle {
255256
#content;
257+
#size;
256258
#entry;
257259
#getStats;
258260

@@ -273,6 +275,7 @@ class MemoryFileHandle extends VirtualFileHandle {
273275
constructor(path, flags, mode, content, entry, getStats) {
274276
super(path, flags, mode);
275277
this.#content = content;
278+
this.#size = content.length;
276279
this.#entry = entry;
277280
this.#getStats = getStats;
278281

@@ -281,13 +284,14 @@ class MemoryFileHandle extends VirtualFileHandle {
281284
flags === 'wx' || flags === 'wx+') {
282285
// Write mode: truncate
283286
this.#content = Buffer.alloc(0);
287+
this.#size = 0;
284288
if (entry) {
285289
entry.content = this.#content;
286290
}
287291
} else if (flags === 'a' || flags === 'a+' ||
288292
flags === 'ax' || flags === 'ax+') {
289293
// Append mode: position at end
290-
this.position = this.#content.length;
294+
this.position = this.#size;
291295
}
292296
}
293297

@@ -329,7 +333,7 @@ class MemoryFileHandle extends VirtualFileHandle {
329333
if (this.#entry?.isDynamic && this.#entry.isDynamic()) {
330334
return this.#entry.getContentSync();
331335
}
332-
return this.#content;
336+
return this.#content.subarray(0, this.#size);
333337
}
334338

335339
/**
@@ -404,23 +408,30 @@ class MemoryFileHandle extends VirtualFileHandle {
404408

405409
// In append mode, always write at the end
406410
const writePos = this.#isAppend() ?
407-
this.#content.length :
411+
this.#size :
408412
(position !== null && position !== undefined ? position : this.position);
409413
const data = buffer.subarray(offset, offset + length);
410414

411-
// Expand content if needed
412-
if (writePos + length > this.#content.length) {
413-
const newContent = Buffer.alloc(writePos + length);
414-
this.#content.copy(newContent, 0, 0, this.#content.length);
415+
// Expand buffer if needed (geometric doubling for amortized O(1) appends)
416+
const neededSize = writePos + length;
417+
if (neededSize > this.#content.length) {
418+
const newCapacity = MathMax(neededSize, this.#content.length * 2);
419+
const newContent = Buffer.alloc(newCapacity);
420+
this.#content.copy(newContent, 0, 0, this.#size);
415421
this.#content = newContent;
416422
}
417423

418424
// Write the data
419425
data.copy(this.#content, writePos);
420426

427+
// Update actual content size
428+
if (neededSize > this.#size) {
429+
this.#size = neededSize;
430+
}
431+
421432
// Update the entry's content and mtime
422433
if (this.#entry) {
423-
this.#entry.content = this.#content;
434+
this.#entry.content = this.#content.subarray(0, this.#size);
424435
this.#entry.mtime = DateNow();
425436
}
426437

@@ -495,21 +506,27 @@ class MemoryFileHandle extends VirtualFileHandle {
495506

496507
// In append mode, append to existing content
497508
if (this.flags === 'a' || this.flags === 'a+') {
498-
const newContent = Buffer.alloc(this.#content.length + buffer.length);
499-
this.#content.copy(newContent, 0);
500-
buffer.copy(newContent, this.#content.length);
501-
this.#content = newContent;
509+
const neededSize = this.#size + buffer.length;
510+
if (neededSize > this.#content.length) {
511+
const newCapacity = MathMax(neededSize, this.#content.length * 2);
512+
const newContent = Buffer.alloc(newCapacity);
513+
this.#content.copy(newContent, 0, 0, this.#size);
514+
this.#content = newContent;
515+
}
516+
buffer.copy(this.#content, this.#size);
517+
this.#size = neededSize;
502518
} else {
503519
this.#content = Buffer.from(buffer);
520+
this.#size = buffer.length;
504521
}
505522

506523
// Update the entry's content and mtime
507524
if (this.#entry) {
508-
this.#entry.content = this.#content;
525+
this.#entry.content = this.#content.subarray(0, this.#size);
509526
this.#entry.mtime = DateNow();
510527
}
511528

512-
this.position = this.#content.length;
529+
this.position = this.#size;
513530
}
514531

515532
/**
@@ -530,7 +547,7 @@ class MemoryFileHandle extends VirtualFileHandle {
530547
statSync(options) {
531548
this.#checkClosed();
532549
if (this.#getStats) {
533-
return this.#getStats(this.#content.length);
550+
return this.#getStats(this.#size);
534551
}
535552
throw new ERR_INVALID_STATE('stats not available');
536553
}
@@ -552,17 +569,25 @@ class MemoryFileHandle extends VirtualFileHandle {
552569
this.#checkClosed();
553570
this.#checkWritable();
554571

555-
if (len < this.#content.length) {
556-
this.#content = this.#content.subarray(0, len);
557-
} else if (len > this.#content.length) {
558-
const newContent = Buffer.alloc(len);
559-
this.#content.copy(newContent, 0, 0, this.#content.length);
560-
this.#content = newContent;
572+
if (len < this.#size) {
573+
// Zero out truncated region to avoid stale data
574+
this.#content.fill(0, len, this.#size);
575+
this.#size = len;
576+
} else if (len > this.#size) {
577+
if (len > this.#content.length) {
578+
const newContent = Buffer.alloc(len);
579+
this.#content.copy(newContent, 0, 0, this.#size);
580+
this.#content = newContent;
581+
} else {
582+
// Buffer has enough capacity, just zero-fill the extension
583+
this.#content.fill(0, this.#size, len);
584+
}
585+
this.#size = len;
561586
}
562587

563588
// Update the entry's content and mtime
564589
if (this.#entry) {
565-
this.#entry.content = this.#content;
590+
this.#entry.content = this.#content.subarray(0, this.#size);
566591
this.#entry.mtime = DateNow();
567592
}
568593
}

0 commit comments

Comments
 (0)