raszi / node-tmp

Temporary file and directory creator for node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

unsafeCleanup fails on windows

rDarge opened this issue · comments

Operating System

  • Linux
  • Windows 7
  • Windows 10
  • MacOS

NodeJS Version

  • 17

Tmp Version

0.2.1

Expected Behavior

I expect node-tmp to recursively delete contents of the folder and then the folder itself

Experienced Behavior

node-tmp appears to delete all of the contents of the folder and then error, with the following stack trace:

Error: ENOTEMPTY: directory not empty, rmdir 'C:\Users\Me\AppData\Local\Temp\tmp-1920-PmZ4Htrvkfa6'
    at Object.rmdirSync (node:fs:1167:10)
    at rmkidsSync (C:\Users\Ryan\Documents\Github\kai-backend\node_modules\rimraf\rimraf.js:349:27)
    at rmdirSync (C:\Users\Ryan\Documents\Github\kai-backend\node_modules\rimraf\rimraf.js:329:7)
    at rimrafSync (C:\Users\Ryan\Documents\Github\kai-backend\node_modules\rimraf\rimraf.js:301:9)
    at Object._cleanupCallback [as removeCallback] (C:\Users\Ryan\Documents\Github\kai-backend\node_modules\tmp\lib\tmp.js:356:16)
    at Object.<anonymous> (C:\Users\Ryan\Documents\Github\kai-backend\routes\working-folder.ts:24:15)
    at Generator.next (<anonymous>)
    at fulfilled (C:\Users\Ryan\Documents\Github\kai-backend\routes\working-folder.ts:5:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

Attempting to remove the directory using unsafeCleanup: false results in nearly identical behavior:

Error: ENOTEMPTY: directory not empty, rmdir 'C:\Users\Ryan\AppData\Local\Temp\tmp-18508-Ogmop5DSalF4'
    at Object.rmdirSync (node:fs:1167:10)
    at Object._cleanupCallback [as removeCallback] (C:\Users\Ryan\Documents\Github\kai-backend\node_modules\tmp\lib\tmp.js:356:16)
    at Object.<anonymous> (C:\Users\Ryan\Documents\Github\kai-backend\routes\working-folder.ts:24:15)
    at Generator.next (<anonymous>)
    at fulfilled (C:\Users\Ryan\Documents\Github\kai-backend\routes\working-folder.ts:5:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

Looking at the rimraf.js source code in the area where the exception was thrown, I see:

  // We only end up here once we got ENOTEMPTY at least once, and
  // at this point, we are guaranteed to have removed all the kids.
  // So, we know that it won't be ENOENT or ENOTDIR or anything else.
  // try really hard to delete stuff on windows, because it has a
  // PROFOUNDLY annoying habit of not closing handles promptly when
  // files are deleted, resulting in spurious ENOTEMPTY errors.

I've slapped together a fix by retrying "removeCallback()" if it fails the first time.

try {
    dir.removeCallback();
} catch (er) {
    console.log("could not remove temp directory, trying again", er);
    //Will often fail the first time on windows, not so often the second time.
    dir.removeCallback();
}

@rDarge I am wondering, do you have async code that is still operating on a file inside the temporary folder?
If that is the case, then this might be a race condition...

Hey @silkentrance ! Thanks for checking in. All async code should have resolved by the time I removed the callback; I remember looking into it at the time and arrived at the conclusion that windows sometimes hangs on to recently opened files briefly after a process releases a lock on them...

our prod machine is running linux and hasn't had any issues with this though

@rDarge Thanks for the quick feedback. Windows, sigh. Maybe relying on CI tests run in a target platform environment instead of relying on locally run tests will do the trick.

I would not know on how to deal with this issue, where Windows is delaying the deletion of the file lock to after that a specific process terminates...

See the workaround provided by @rDarge.

Would it be possible to document this behaviour somewhere? I though I was going crazy when my tests were passing on macOS and CI (Linux) but not on Windows.