mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
fs.writeFile(path, data) took three libuv thread pool round trips (open, write, close), each its own request with its own queue wait, completion callback and JS/C++ crossing, and fs.promises.writeFile() did the same through a FileHandle. For the small files applications write most, the round trips are the cost, and each occupies a pool slot that concurrent fs, dns.lookup() and crypto work is also queueing for. Add WriteFileJob next to ReadFileJob: an AsyncWrap + ThreadPoolWork that opens, writes the whole buffer (looping on short writes) and closes as one pool task, keeping the buffer alive until it is done. fs.writeFile() uses it for path arguments without flush; fs.promises.writeFile() additionally keeps data above one write chunk (and iterables) on the FileHandle path, so large writes stay abortable between chunks as before. File descriptors, FileHandles, flush: true and an active VFS keep their existing paths. Behavior is otherwise kept: open failures report syscall 'open' with the path, write failures 'write'; permission errors are delivered through the callback/promise; an abort signalled while the write is in flight is still reported as an AbortError; the job is an FSREQCALLBACK resource for async_hooks and emits the 'write' fs trace event. Tests that used fs.writeFile() as a proxy for open/close trace events, or injected FileHandle faults for path-based writes, are adjusted to keep testing what they test. The job holds the buffer's backing store, so the memory stays valid if the buffer is detached or collected before the write finishes; a resizable ArrayBuffer could still have its pages decommitted by a shrink, so its contents are copied when the job is created. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65489 Refs: https://github.com/nodejs/node/pull/65327 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
77 lines
2.4 KiB
JavaScript
77 lines
2.4 KiB
JavaScript
'use strict';
|
|
// Flags: --expose-internals
|
|
|
|
const common = require('../common');
|
|
const tmpdir = require('../common/tmpdir');
|
|
|
|
// The following tests validate aggregate errors are thrown correctly
|
|
// when both an operation and close throw.
|
|
|
|
const {
|
|
readFile,
|
|
writeFile,
|
|
truncate,
|
|
lchmod,
|
|
} = require('fs/promises');
|
|
const {
|
|
FileHandle,
|
|
} = require('internal/fs/promises');
|
|
|
|
const assert = require('assert');
|
|
const originalFd = Object.getOwnPropertyDescriptor(FileHandle.prototype, 'fd');
|
|
|
|
let count = 0;
|
|
async function createFile() {
|
|
const filePath = tmpdir.resolve(`aggregate_errors_${++count}.txt`);
|
|
// Larger than one read chunk (512 KiB), so that readFile(path) reads it
|
|
// through a FileHandle (small files are read in a single native round trip
|
|
// that does not involve FileHandle.prototype).
|
|
await writeFile(filePath, 'content'.repeat(100_000));
|
|
return filePath;
|
|
}
|
|
|
|
async function checkAggregateError(op) {
|
|
try {
|
|
const filePath = await createFile();
|
|
Object.defineProperty(FileHandle.prototype, 'fd', {
|
|
get: function() {
|
|
// Close is set by using a setter,
|
|
// so it needs to be set on the instance.
|
|
const originalClose = this.close;
|
|
this.close = async () => {
|
|
// close the file
|
|
await originalClose.call(this);
|
|
const closeError = new Error('CLOSE_ERROR');
|
|
closeError.code = 456;
|
|
throw closeError;
|
|
};
|
|
const opError = new Error('INTERNAL_ERROR');
|
|
opError.code = 123;
|
|
throw opError;
|
|
}
|
|
});
|
|
|
|
await assert.rejects(op(filePath), common.mustCall((err) => {
|
|
assert.strictEqual(err.name, 'AggregateError');
|
|
assert.strictEqual(err.code, 123);
|
|
assert.strictEqual(err.errors.length, 2);
|
|
assert.strictEqual(err.errors[0].message, 'INTERNAL_ERROR');
|
|
assert.strictEqual(err.errors[1].message, 'CLOSE_ERROR');
|
|
return true;
|
|
}));
|
|
} finally {
|
|
Object.defineProperty(FileHandle.prototype, 'fd', originalFd);
|
|
}
|
|
}
|
|
(async function() {
|
|
tmpdir.refresh();
|
|
await checkAggregateError((filePath) => truncate(filePath));
|
|
await checkAggregateError((filePath) => readFile(filePath));
|
|
// More than one write chunk (512 KiB), so that writeFile(path) goes through
|
|
// a FileHandle as well.
|
|
await checkAggregateError((filePath) => writeFile(filePath, '123'.repeat(200_000)));
|
|
if (common.isMacOS) {
|
|
await checkAggregateError((filePath) => lchmod(filePath, 0o777));
|
|
}
|
|
})().then(common.mustCall());
|