Skip to content

Inconsistent retryDelay in fs.rmSync and asynchronous fs.rm (on Windows) #64016

@louiellan

Description

@louiellan

Version

v24.17.0

Platform

Microsoft Windows NT 10.0.26200.0 x64

Subsystem

fs

What steps will reproduce the bug?

The current workspace

eperm-folder
|---> locked-file.txt
locking.py
index.js

locking.py

import os, signal
os.makedirs("eperm-folder", exist_ok=True)

filepath = "eperm-folder/locked-file.txt"
print(f"locking {filepath} indefinitely, until SIGINT")
fd = os.open(filepath, os.O_RDWR | os.O_CREAT )
os.write(fd, b'Hello, World!')
def sigterm_handler(s, f):
    os.close(fd)
    exit(0)
signal.signal(signal.SIGINT, sigterm_handler)
while True:
    pass

index.js

const fs = require('node:fs');
const fsPromises = require('node:fs/promises');
const path = require('node:path')
const { spawn } = require('node:child_process')
const filepath = "eperm-folder/locked-file.txt"
if (fs.existsSync(filepath)) {
    console.log('deleting eperm-folder/')
    const start = performance.now()
    try { 
        fs.rmSync(path.dirname(filepath), { recursive: true, retryDelay: 5 * 1000, maxRetries: 1 })
    }
    catch (e){
        console.log(e)
        const duration = performance.now() - start;
        console.log(`rmSync - ${duration} ms`)
    }
}

Steps to run:

  1. Run python locking.py first
  • this is to create eperm-folder/locked-file.txt and to indefinitely lock the file (simulating an EPERM/EBUSY error)
  1. Then run node index.js

How often does it reproduce? Is there a required condition?

Always, this is specific to Windows, and only when the file / directory is locked by another process.

What is the expected behavior? Why is that the expected behavior?

The time elapsed should be more than 5000ms given it is our retryDelay value

For comparison, in the asynchronous fs.rm functions, the time elapsed is around 15,000ms which is what we expect

Here's a reproducible script for the asynchronous fs.rm functions

const fs = require('node:fs');
const fsPromises = require('node:fs/promises');
const path = require('node:path')
const filepath = "eperm-folder/locked-file.txt"

if (fs.existsSync(filepath)) {
    console.log('deleting eperm-folder/')
    const start = performance.now()
        fs.rm(
            path.dirname(filepath), 
            { recursive: true, retryDelay: 5 * 1000, maxRetries: 1 },
            (e) => {
                if (e)
                    console.error(e)
                const duration = performance.now() - start;
                console.log(`async fs.rm() - ${duration} ms`)
            }
        )
}

This means that in the fs.rmSync, retries are not delayed

What do you see instead?

The time elapsed in the fs.rmSync operation is between 1-30 ms (on my end)

Additional information

This affects other retryable errors in Windows (e.g., EMFILE, ENFILE, ENOTEMPTY)
Might be related to #55555, but this is Windows-specific

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions