diff --git a/lib/internal/fs/rimraf.js b/lib/internal/fs/rimraf.js index 6d6b19efcc98f2..3402af60a4022f 100644 --- a/lib/internal/fs/rimraf.js +++ b/lib/internal/fs/rimraf.js @@ -5,6 +5,7 @@ // - Bring your own custom fs module is not currently supported. // - Some basic code cleanup. 'use strict'; +const { Buffer } = require('buffer'); const { chmod, chmodSync, @@ -19,7 +20,7 @@ const { unlink, unlinkSync } = require('fs'); -const { join } = require('path'); +const { sep } = require('path'); const { setTimeout } = require('timers'); const { sleep } = require('internal/util'); const notEmptyErrorCodes = new Set(['ENOTEMPTY', 'EEXIST', 'EPERM']); @@ -28,6 +29,8 @@ const retryErrorCodes = new Set( const isWindows = process.platform === 'win32'; const epermHandler = isWindows ? fixWinEPERM : _rmdir; const epermHandlerSync = isWindows ? fixWinEPERMSync : _rmdirSync; +const readdirEncoding = 'buffer'; +const separator = Buffer.from(sep); function rimraf(path, options, callback) { @@ -116,7 +119,9 @@ function _rmdir(path, options, originalErr, callback) { function _rmchildren(path, options, callback) { - readdir(path, (err, files) => { + const pathBuf = Buffer.from(path); + + readdir(pathBuf, readdirEncoding, (err, files) => { if (err) return callback(err); @@ -128,7 +133,9 @@ function _rmchildren(path, options, callback) { let done = false; files.forEach((child) => { - rimraf(join(path, child), options, (err) => { + const childPath = Buffer.concat([pathBuf, separator, child]); + + rimraf(childPath, options, (err) => { if (done) return; @@ -177,7 +184,7 @@ function rimrafSync(path, options) { if (stats !== undefined && stats.isDirectory()) _rmdirSync(path, options, null); else - unlinkSync(path); + _unlinkSync(path, options); } catch (err) { if (err.code === 'ENOENT') return; @@ -191,6 +198,25 @@ function rimrafSync(path, options) { } +function _unlinkSync(path, options) { + const tries = options.maxRetries + 1; + + for (let i = 1; i <= tries; i++) { + try { + return unlinkSync(path); + } catch (err) { + // Only sleep if this is not the last try, and the delay is greater + // than zero, and an error was encountered that warrants a retry. + if (retryErrorCodes.has(err.code) && + i < tries && + options.retryDelay > 0) { + sleep(i * options.retryDelay); + } + } + } +} + + function _rmdirSync(path, options, originalErr) { try { rmdirSync(path); @@ -205,8 +231,12 @@ function _rmdirSync(path, options, originalErr) { // original removal. Windows has a habit of not closing handles promptly // when files are deleted, resulting in spurious ENOTEMPTY failures. Work // around that issue by retrying on Windows. - readdirSync(path).forEach((child) => { - rimrafSync(join(path, child), options); + const pathBuf = Buffer.from(path); + + readdirSync(pathBuf, readdirEncoding).forEach((child) => { + const childPath = Buffer.concat([pathBuf, separator, child]); + + rimrafSync(childPath, options); }); const tries = options.maxRetries + 1; @@ -253,7 +283,7 @@ function fixWinEPERMSync(path, options, originalErr) { if (stats.isDirectory()) _rmdirSync(path, options, originalErr); else - unlinkSync(path); + _unlinkSync(path, options); } diff --git a/test/common/tmpdir.js b/test/common/tmpdir.js index 16d375afff06b1..0b8465bb22fc73 100644 --- a/test/common/tmpdir.js +++ b/test/common/tmpdir.js @@ -36,60 +36,12 @@ function rimrafSync(pathname, { spawn = true } = {}) { } } - try { - if (st.isDirectory()) - rmdirSync(pathname, null); - else - fs.unlinkSync(pathname); - } catch (e) { - debug(e); - switch (e.code) { - case 'ENOENT': - // It's not there anymore. Work is done. Exiting. - return; - - case 'EPERM': - // This can happen, try again with `rmdirSync`. - break; - - case 'EISDIR': - // Got 'EISDIR' even after testing `st.isDirectory()`... - // Try again with `rmdirSync`. - break; - - default: - throw e; - } - rmdirSync(pathname, e); - } + fs.rmdirSync(pathname, { recursive: true, maxRetries: 5 }); if (fs.existsSync(pathname)) throw new Error(`Unable to rimraf ${pathname}`); } -function rmdirSync(p, originalEr) { - try { - fs.rmdirSync(p); - } catch (e) { - if (e.code === 'ENOTDIR') - throw originalEr; - if (e.code === 'ENOTEMPTY' || e.code === 'EEXIST' || e.code === 'EPERM') { - const enc = process.platform === 'linux' ? 'buffer' : 'utf8'; - fs.readdirSync(p, enc).forEach((f) => { - if (f instanceof Buffer) { - const buf = Buffer.concat([Buffer.from(p), Buffer.from(path.sep), f]); - rimrafSync(buf); - } else { - rimrafSync(path.join(p, f)); - } - }); - fs.rmdirSync(p); - return; - } - throw e; - } -} - const testRoot = process.env.NODE_TEST_DIR ? fs.realpathSync(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..');