GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fs: treat `std::errc::permission_denied` as `EPERM` error · nodejs/node@2de845b

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • src
    • node_file.cc
  • test/parallel
    • test-fs-rmSync-eperm-retries.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/node_file.cc‎

Copy file name to clipboardExpand all lines: src/node_file.cc
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,9 @@ static void RmSync(const FunctionCallbackInfo<Value>& args) {
17931793
error == std::errc::too_many_files_open ||
17941794
error == std::errc::too_many_files_open_in_system ||
17951795
error == std::errc::directory_not_empty ||
1796+
#ifdef _WIN32
1797+
error == std::errc::permission_denied ||
1798+
#endif
17961799
error == std::errc::operation_not_permitted);
17971800
};
17981801

@@ -1813,8 +1816,10 @@ static void RmSync(const FunctionCallbackInfo<Value>& args) {
18131816

18141817
if (retryDelay > 0) {
18151818
#ifdef _WIN32
1816-
Sleep(i * retryDelay / 1000);
1819+
// No conversion needed: Sleep() takes milliseconds.
1820+
Sleep(i * retryDelay);
18171821
#else
1822+
// sleep() takes seconds, so convert the millisecond delay.
18181823
sleep(i * retryDelay / 1000);
18191824
#endif
18201825
}
Collapse file

‎test/parallel/test-fs-rmSync-eperm-retries.js‎

Copy file name to clipboard
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (!common.isWindows)
6+
common.skip('Windows-specific: EPERM sharing-violation retry in rmSync');
7+
8+
const tmpdir = require('../common/tmpdir');
9+
const assert = require('assert');
10+
const { once } = require('events');
11+
const { spawn } = require('child_process');
12+
const fs = require('fs');
13+
const path = require('path');
14+
15+
tmpdir.refresh();
16+
17+
// UV_FS_O_EXLOCK opens with share mode 0, so deletion fails with EPERM
18+
// until this process kills the child.
19+
const lockerScript = `
20+
const fs = require('fs');
21+
const UV_FS_O_EXLOCK = 0x10000000;
22+
fs.openSync(process.argv[1], fs.constants.O_RDWR | UV_FS_O_EXLOCK);
23+
process.stdout.write('locked');
24+
setInterval(() => {}, 60_000);
25+
`;
26+
27+
async function spawnLocker(file) {
28+
const child = spawn(process.execPath, ['-e', lockerScript, file],
29+
{ stdio: ['ignore', 'pipe', 'inherit'] });
30+
const [data] = await once(child.stdout, 'data');
31+
assert.strictEqual(data.toString(), 'locked');
32+
return child;
33+
}
34+
35+
// Sleep before retry i is i * retryDelay ms, so all retries take at least
36+
// retryDelay * (1 + 2 + ... + maxRetries) ms.
37+
function minRetryTime({ maxRetries, retryDelay }) {
38+
return retryDelay * maxRetries * (maxRetries + 1) / 2;
39+
}
40+
41+
function timedRmThrowsEPERM(dir, options) {
42+
const start = Date.now();
43+
assert.throws(() => {
44+
fs.rmSync(dir, { recursive: true, ...options });
45+
}, {
46+
code: 'EPERM',
47+
name: 'Error',
48+
syscall: 'rm',
49+
});
50+
return Date.now() - start;
51+
}
52+
53+
(async () => {
54+
const dir = tmpdir.resolve('rm-eperm-retries');
55+
const file = path.join(dir, 'locked.txt');
56+
fs.mkdirSync(dir);
57+
fs.writeFileSync(file, 'hello');
58+
59+
const child = await spawnLocker(file);
60+
try {
61+
// Proves the lock is effective: no retries means an immediate EPERM.
62+
timedRmThrowsEPERM(dir, { maxRetries: 0, retryDelay: 0 });
63+
assert.strictEqual(fs.existsSync(file), true);
64+
65+
const options = { maxRetries: 4, retryDelay: 100 };
66+
const expected = minRetryTime(options); // 100+200+300+400 = 1000 ms.
67+
const elapsed = timedRmThrowsEPERM(dir, options);
68+
69+
// Windows timer granularity may shave a few ms off each Sleep() call.
70+
const slack = 16 * options.maxRetries;
71+
assert.ok(elapsed >= expected - slack,
72+
`rmSync() gave up after ${elapsed}ms; expected it to spend at ` +
73+
`least ~${expected}ms on ${options.maxRetries} retries of ` +
74+
`${options.retryDelay}ms escalating delay`);
75+
76+
// Catches unit confusion (e.g. seconds vs. milliseconds) in the delay.
77+
assert.ok(elapsed < common.platformTimeout(expected * 10),
78+
`rmSync() gave up after ${elapsed}ms; expected roughly ` +
79+
`${expected}ms for ${options.maxRetries} retries`);
80+
} finally {
81+
child.kill();
82+
}
83+
await once(child, 'exit');
84+
assert.strictEqual(fs.existsSync(file), true);
85+
})().then(common.mustCall());

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.