fs: make readdir recursive algorithm iterative

PR-URL: https://github.com/nodejs/node/pull/47650
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
This commit is contained in:
Ethan Arrowood 2023-05-10 01:00:40 -07:00 committed by GitHub
parent ded6b618be
commit 12a93ce630
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 32 deletions

View File

@ -27,6 +27,7 @@
const { const {
ArrayPrototypePush, ArrayPrototypePush,
BigIntPrototypeToString, BigIntPrototypeToString,
Boolean,
MathMax, MathMax,
Number, Number,
ObjectDefineProperties, ObjectDefineProperties,
@ -97,6 +98,7 @@ const {
copyObject, copyObject,
Dirent, Dirent,
emitRecursiveRmdirWarning, emitRecursiveRmdirWarning,
getDirent,
getDirents, getDirents,
getOptions, getOptions,
getValidatedFd, getValidatedFd,
@ -1404,34 +1406,60 @@ function mkdirSync(path, options) {
} }
} }
// TODO(Ethan-Arrowood): Make this iterative too /**
function readdirSyncRecursive(path, origPath, options) { * An iterative algorithm for reading the entire contents of the `basePath` directory.
nullCheck(path, 'path', true); * This function does not validate `basePath` as a directory. It is passed directly to
const ctx = { path }; * `binding.readdir` after a `nullCheck`.
const result = binding.readdir(pathModule.toNamespacedPath(path), * @param {string} basePath
options.encoding, !!options.withFileTypes, undefined, ctx); * @param {{ encoding: string, withFileTypes: boolean }} options
handleErrorFromBinding(ctx); * @returns {string[] | Dirent[]}
return options.withFileTypes ? */
getDirents(path, result).flatMap((dirent) => { function readdirSyncRecursive(basePath, options) {
return [ nullCheck(basePath, 'path', true);
dirent,
...(dirent.isDirectory() ? const withFileTypes = Boolean(options.withFileTypes);
readdirSyncRecursive( const encoding = options.encoding;
pathModule.join(path, dirent.name),
origPath, const readdirResults = [];
options, const pathsQueue = [basePath];
) : []),
]; const ctx = { path: basePath };
}) : function read(path) {
result.flatMap((ent) => { ctx.path = path;
const innerPath = pathModule.join(path, ent); const readdirResult = binding.readdir(
const relativePath = pathModule.relative(origPath, innerPath); pathModule.toNamespacedPath(path),
const stat = binding.internalModuleStat(innerPath); encoding,
return [ withFileTypes,
relativePath, undefined,
...(stat === 1 ? readdirSyncRecursive(innerPath, origPath, options) : []), ctx,
]; );
}); handleErrorFromBinding(ctx);
for (let i = 0; i < readdirResult.length; i++) {
if (withFileTypes) {
const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
ArrayPrototypePush(readdirResults, dirent);
if (dirent.isDirectory()) {
ArrayPrototypePush(pathsQueue, pathModule.join(dirent.path, dirent.name));
}
} else {
const resultPath = pathModule.join(path, readdirResult[i]);
const relativeResultPath = pathModule.relative(basePath, resultPath);
const stat = binding.internalModuleStat(resultPath);
ArrayPrototypePush(readdirResults, relativeResultPath);
// 1 indicates directory
if (stat === 1) {
ArrayPrototypePush(pathsQueue, resultPath);
}
}
}
}
for (let i = 0; i < pathsQueue.length; i++) {
read(pathsQueue[i]);
}
return readdirResults;
} }
/** /**
@ -1456,7 +1484,7 @@ function readdir(path, options, callback) {
} }
if (options.recursive) { if (options.recursive) {
callback(null, readdirSyncRecursive(path, path, options)); callback(null, readdirSyncRecursive(path, options));
return; return;
} }
@ -1494,7 +1522,7 @@ function readdirSync(path, options) {
} }
if (options.recursive) { if (options.recursive) {
return readdirSyncRecursive(path, path, options); return readdirSyncRecursive(path, options);
} }
const ctx = { path }; const ctx = { path };

View File

@ -160,8 +160,6 @@ class Dir {
} }
} }
// TODO(Ethan-Arrowood): Review this implementation. Make it iterative.
// Can we better leverage the `kDirOperationQueue`?
readSyncRecursive(dirent) { readSyncRecursive(dirent) {
const ctx = { path: dirent.path }; const ctx = { path: dirent.path };
const handle = dirBinding.opendir( const handle = dirBinding.opendir(