| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 83a7949 commit 2de845b
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1793,6 +1793,9 @@ static void RmSync(const FunctionCallbackInfo<Value>& args) { | |||
| 1793 | 1793 | error == std::errc::too_many_files_open || | |
| 1794 | 1794 | error == std::errc::too_many_files_open_in_system || | |
| 1795 | 1795 | error == std::errc::directory_not_empty || | |
| 1796 | + #ifdef _WIN32 | ||
| 1797 | + error == std::errc::permission_denied || | ||
| 1798 | + #endif | ||
| 1796 | 1799 | error == std::errc::operation_not_permitted); | |
| 1797 | 1800 | }; | |
| 1798 | 1801 | ||
@@ -1813,8 +1816,10 @@ static void RmSync(const FunctionCallbackInfo<Value>& args) { | |||
| 1813 | 1816 | ||
| 1814 | 1817 | if (retryDelay > 0) { | |
| 1815 | 1818 | #ifdef _WIN32 | |
| 1816 | - Sleep(i * retryDelay / 1000); | ||
| 1819 | + // No conversion needed: Sleep() takes milliseconds. | ||
| 1820 | + Sleep(i * retryDelay); | ||
| 1817 | 1821 | #else | |
| 1822 | + // sleep() takes seconds, so convert the millisecond delay. | ||
| 1818 | 1823 | sleep(i * retryDelay / 1000); | |
| 1819 | 1824 | #endif | |
| 1820 | 1825 | } | |
| Original file line number | Diff line number | Diff 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()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments