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: add disposable mkdtemp by bakkot · Pull Request #58516 · nodejs/node

Conversation

@bakkot

@bakkot bakkot commented May 30, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Adds fs.mkdtempDisposableSync and fs.promises.mkdtempDisposable, which work like mkdTemp except that they return a disposable object which removes the directory on disposal.

Fixes #58486.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bakkot
fs: add disposable mkdtempSync
5f9db1e
@nodejs-github-bot nodejs-github-bot added fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. labels May 30, 2025
@bakkot bakkot mentioned this pull request May 30, 2025
disposable temporary directory #58486
Closed
@jasnell

jasnell commented May 30, 2025

Copy link
Copy Markdown
Member

I've added an option to the existing mkdtempSync (and will add one to the fs/promises version of mkdtemp) rather than adding a new function. This has the effect of making the return type depend on the options, which is weird to me but follows e.g. the withFileTypes option on readdir.

I've never been a fan of this kind of polymorphic return but won't block on it. Among the key issues is the fact that it is not discoverable. That is, I can't do something like if (fs.makeDisposableTempDir) ... to detect support. But in this case, since using will refuse to work with a noisy runtime error if the user gets it wrong, this should be ok? Still don't like it tho.

cjihrig, himself65, cclauss, tniessen, and eugene1g reacted with thumbs up emoji
All reactions
  • 👍 5 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bakkot

bakkot commented May 30, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

If you'd prefer a new mkdtempDisposable method I'm happy to switch to that. I also don't like this kind of polymorphic return (and have argued against it for new JS methods).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jasnell

jasnell commented May 30, 2025

Copy link
Copy Markdown
Member

Let's see if we can get more folks to comment on it before going through the trouble to change it. @nodejs/collaborators

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Renegade334

Renegade334 commented May 30, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Own musings:

+1 to the principle, but before starting down this road in earnest, there is probably the opportunity to agree on some kind of design language for "single-use disposables", to keep things consistent across the API rather than drip-feeding a load of independently-designed changes with different semantics.

I'd add a -1 for the polymorphic approach. A "disposable" option on the existing method doesn't just change how the resource is presented (eg. withFileTypes), it completely changes how that returned resource is handled by the user, which feels like more of an existential difference than befits an options parameter. There's also the fact that not all candidate APIs could be modified in this way (eg. eventEmitter.on(), which doesn't have scope to accept an options argument), so this would be committing to at least two different conventions (polymorphism versus separate method).

since using will refuse to work with a noisy runtime error if the user gets it wrong, this should be ok?

This argument might apply here, but it wouldn't necessarily if the same paradigm were applied to other APIs that might return null or undefined.

pimterry, jasnell, himself65, jazelly, and eugene1g reacted with thumbs up emoji
All reactions
  • 👍 5 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@aduh95

aduh95 commented May 30, 2025

Copy link
Copy Markdown
Contributor

If we ever get to resurrect #33549, it'd be nice to think of an API design that could be applied to both. I don't have a suggestion, avoiding polymorphism would be great indeed.

tniessen reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bakkot

bakkot commented May 31, 2025

Copy link
Copy Markdown
Contributor Author

Re: design language for single-use disposables, I think that for almost all cases (in node or elsewhere) I'd recommend the following:

  • the disposable has a string-named method for cleanup, and Symbol.dispose is an alias for it
  • calling the cleanup method again after it has already been called does nothing and does not throw

In most cases it doesn't make sense to put "disposable" in the name of the creation method (and node already has a handful of disposables which are not so named); just call it whatever you'd normally call it. For example, mkstemp could return a disposable.

Unfortunately mkdtempSync returns a string, which cannot be made disposable. That means either doing the polymorphic return that no one is enthusiastic about or adding a new method, and I don't have any good ideas for the method name. Suggestions welcome. Maybe we could say that whateverDisposable() is the way we make a disposable version of primitive-returning APIs? But that doesn't feel great. Which is why I went with the polymorphic return.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jasnell

jasnell commented May 31, 2025

Copy link
Copy Markdown
Member

That's reasonable, I believe. There are a number of other guidelines we should have, I think.. such as making sure that disposers are always idempotent, and following the conversation we had in the tc39 matrix earlier today, including the assumption that when disposal can be clean (not an error path) or dirty (an exception is thrown and pending), then clean disposal should always be explicit and the code in the disposer should generally assume that there's a pending error. So, for instance,

class MyDisposable {
  doSomething() { /* ... */ }
  close() { /* ... * / }
  abort() { /* ... * / }
  get closed() { /* ... * / }
  [Symbol.dispose]() { if (!this.closed) this.abort() }

  // Or, in the case of something like streams... destroy([error]) where passing an error
  // indicates disposal with a pending exception, while passing nothing indicates clean
  // disposal.
}
{
  using m = new MyDisposable();
  { 
    using n = m;  // causes dispose to be called twice... therefore dispose needs to be idempotent
  }
  m.doSomething();
  m.close();  // explicit clean disposal ... a lot like us requiring that FileHandle is explicitly closed
}

As for this particular method, I think having an awkwardly named mkdtempDisposable is far better than having a polymorphic return. We already have a bunch of awkwardly named API alternatives (mkdtemp, mkdtempSync)... what's a few more?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@himself65

himself65 commented May 31, 2025

Copy link
Copy Markdown
Member

I personally think dispoable could be a utility, similar with utils.promisify, it could be utils.disposify(entryFn, cleanupFn): () => { resource: ReturnType<entryFn>, cleanup: cleanupFn, [Symbol.dispose]: cleanupFn }

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bakkot

bakkot commented May 31, 2025

Copy link
Copy Markdown
Contributor Author

I don't think there's an obvious way to do that here? It's not enough to know that you want to clean up with rmSync; you have to actually specify the parameters (in this case, importantly, recursive: true).

Also every time I have to use promisify I am mad about it because I want to just have the nice things available directly to me instead of having to clobber them together.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ShogunPanda

ShogunPanda commented May 31, 2025

Copy link
Copy Markdown
Contributor

I love the idea.
-1 to the polymorphic approach as it might be a mess in TypeScript environments.

pmarchini and jazelly reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cjihrig

cjihrig commented May 31, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

@himself65 I also thought that util.disposable() would be a nice thing to have. However, after implementing it, there are some limitations that util.promisify() doesn't have. The issue is that the cleanup function differs from resource to resource. In this PR, for example, the cleanup function needs access to the result of mkdtemp(), as well as process.cwd() at the time of the original call. I think util.disposable() would either end up being too simple to be useful, or full of so many hook points that the complexity isn't worth it.

Details
'use strict';
const { mkdtempSync, rmSync } = require('node:fs');

function disposable(f) {
  return function(...args) {
    const self = this;
    const result = Reflect.apply(f, self, args);

    return {
      result,
      [Symbol.dispose]() {
        Reflect.apply(f[disposable.custom], self, [result, args]);
      },
    };
  };
}
disposable.custom = Symbol('disposable.custom');

mkdtempSync[disposable.custom] = function(path, args) {
  // Note - does not currently handle saving process.cwd().
  rmSync(path, { recursive: true, force: true });
}

const mkdtempSyncDisposable = disposable(mkdtempSync);

{
  using result = mkdtempSyncDisposable('/tmp/foo-');
}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jasnell jasnell mentioned this pull request May 31, 2025
meta: add guidelines for introduction of ERM support #58526
Merged
@jasnell

jasnell commented May 31, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

To avoid derailing this specific PR further, I've opened a separate PR that seeks to document guidelines for implementing ERM support to existing APIs. I believe it captures the spirit of what has been discussed here: #58526

dario-piotrowicz and mhdawson reacted with thumbs up emoji Renegade334 reacted with rocket emoji
All reactions
  • 👍 2 reactions
  • 🚀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

bakkot added 2 commits May 31, 2025 14:37
@bakkot
switch to mkdtempDisposableSync
55fffe7
@bakkot
add async version
ca15b36
@bakkot

bakkot commented May 31, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

OK, switched to fs.mkdtempDisposableSync and fsPromises.mkdtempDisposable.

I'll get tests up soon exercising at least

  • directory gets made with the expected prefix
  • directory is removed on disposal
    • even if chdir has been called in the mean time
  • disposal is idempotent

and anything else people would like to suggest.

Dunno if it's worth copying over the existing tests, of which there are a variety (x, x, etc).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Renegade334
Renegade334 reviewed Jun 1, 2025
View reviewed changes
Comment thread lib/internal/fs/promises.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/fs/promises.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@tniessen

tniessen commented Jun 5, 2025

Copy link
Copy Markdown
Member

I'll get tests up soon exercising at least (...) and anything else people would like to suggest.

It would be good to add tests to make sure that the dispose function propagates errors during cleanup properly.

Also, it would be good to have test coverage, or at least documentation, for odd edge cases, such as: Is it considered an error if the directory does not exist at the time of disposal? I assume the answer is no to achieve idempotency in some sense. (That also assumes that deletion will be based on the path, not based on the identity of the directory, e.g., using dirfds.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread doc/api/fs.md Resolved

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell reviewed Jun 5, 2025
View reviewed changes
Comment thread lib/fs.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bakkot
address non-test comments
3a3e545
LiviaMedeiros
LiviaMedeiros approved these changes Jun 29, 2025
View reviewed changes
@LiviaMedeiros LiviaMedeiros added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Jun 29, 2025
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Jun 29, 2025
@nodejs-github-bot

This comment was marked as outdated.

Sign in to view
@nodejs-github-bot

This comment was marked as outdated.

Sign in to view
@jasnell jasnell added the author ready PRs with CI started, the required approvals, and no outstanding review comments. label Jun 29, 2025
@nodejs-github-bot

nodejs-github-bot commented Jun 29, 2025

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/67730/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bakkot

bakkot commented Jul 6, 2025

Copy link
Copy Markdown
Contributor Author

This is ready from my end afaict. Anyone with the merge permission want to push the button?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@richardlau richardlau added semver-minor PRs that contain new features and should be released in the next minor version. commit-queue PRs queued for automated landing through the Commit Queue. commit-queue-squash PRs the Commit Queue should land as one squashed commit. labels Jul 6, 2025
@jasnell jasnell removed commit-queue PRs queued for automated landing through the Commit Queue. commit-queue-squash PRs the Commit Queue should land as one squashed commit. labels Jul 7, 2025
jasnell pushed a commit that referenced this pull request Jul 7, 2025
@bakkot @jasnell
fs: add disposable mkdtempSync
9523c84
PR-URL: #58516
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: LiviaMedeiros <[email protected]>
@jasnell

jasnell commented Jul 7, 2025

Copy link
Copy Markdown
Member

Landed in 9523c84

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jasnell jasnell closed this Jul 7, 2025
RafaelGSS pushed a commit that referenced this pull request Jul 8, 2025
@bakkot @RafaelGSS
fs: add disposable mkdtempSync
87f4d07
PR-URL: #58516
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: LiviaMedeiros <[email protected]>
@github-actions github-actions Bot mentioned this pull request Jul 8, 2025
2025-07-09, Version 24.4.0 (Current) #58993
Merged
nodejs-github-bot added a commit that referenced this pull request Jul 8, 2025
@nodejs-github-bot
2025-07-09, Version 24.4.0 (Current)
5576dec
Notable changes:

crypto:
  * (SEMVER-MINOR) support outputLength option in crypto.hash for XOF functions (Aditi) #58121
doc:
  * (SEMVER-MINOR) add all watch-mode related flags to node.1 (Dario Piotrowicz) #58719
fs:
  * (SEMVER-MINOR) add disposable mkdtempSync (Kevin Gibbons) #58516
permission:
  * (SEMVER-MINOR) propagate permission model flags on spawn (Rafael Gonzaga) #58853
sqlite:
  * (SEMVER-MINOR) add support for readBigInts option in db connection level (Miguel Marcondes Filho) #58697
src,permission:
  * (SEMVER-MINOR) add support to permission.has(addon) (Rafael Gonzaga) #58951
watch:
  * (SEMVER-MINOR) add `--watch-kill-signal` flag (Dario Piotrowicz) #58719

PR-URL: #58993
RafaelGSS pushed a commit that referenced this pull request Jul 9, 2025
@nodejs-github-bot @RafaelGSS
2025-07-09, Version 24.4.0 (Current)
c4169cb
Notable changes:

crypto:
  * (SEMVER-MINOR) support outputLength option in crypto.hash for XOF functions (Aditi) #58121
doc:
  * (SEMVER-MINOR) add all watch-mode related flags to node.1 (Dario Piotrowicz) #58719
fs:
  * (SEMVER-MINOR) add disposable mkdtempSync (Kevin Gibbons) #58516
permission:
  * (SEMVER-MINOR) propagate permission model flags on spawn (Rafael Gonzaga) #58853
sqlite:
  * (SEMVER-MINOR) add support for readBigInts option in db connection level (Miguel Marcondes Filho) #58697
src,permission:
  * (SEMVER-MINOR) add support to permission.has(addon) (Rafael Gonzaga) #58951
watch:
  * (SEMVER-MINOR) add `--watch-kill-signal` flag (Dario Piotrowicz) #58719

PR-URL: #58993
aduh95 pushed a commit that referenced this pull request Jul 21, 2025
@bakkot @aduh95
fs: add disposable mkdtempSync
818173d
PR-URL: #58516
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: LiviaMedeiros <[email protected]>
aduh95 pushed a commit that referenced this pull request Jul 24, 2025
@bakkot @aduh95
fs: add disposable mkdtempSync
ebe2be0
PR-URL: #58516
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: LiviaMedeiros <[email protected]>
@aduh95 aduh95 added the backport-requested-v22.x PRs awaiting manual backport to the v22.x-staging branch. label Jul 27, 2025
@aduh95

aduh95 commented Jul 27, 2025

Copy link
Copy Markdown
Contributor

I'm seeing test-wasi fail on Windows with this change on v22.x-staging. If you could open a backport PR, that'd be great

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Renegade334

Renegade334 commented Jul 27, 2025

Copy link
Copy Markdown
Member

I'm seeing test-wasi fail on Windows with this change on v22.x-staging. If you could open a backport PR, that'd be great

Is this #59146?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

robobun pushed a commit to oven-sh/bun that referenced this pull request Aug 23, 2025
Claude Bot @claude
feat(fs): implement mkdtempDisposable and mkdtempDisposableSync
6bcbf04
Implements Node.js-compatible disposable temporary directory functions:
- fs.mkdtempDisposableSync() - synchronous version with Symbol.dispose
- fs.mkdtempDisposable() - callback version with Symbol.asyncDispose
- fs/promises.mkdtempDisposable() - promise version with Symbol.asyncDispose

These functions create temporary directories that can be automatically
cleaned up using JavaScript's disposable syntax (using/await using).

The returned objects include:
- path: string - the created directory path
- remove: (async) function - manual cleanup method
- Symbol.dispose/Symbol.asyncDispose - automatic disposal

Matches Node.js implementation from nodejs/node#58516.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@robobun robobun mentioned this pull request Aug 23, 2025
feat(fs): implement mkdtempDisposable and mkdtempDisposableSync oven-sh/bun#22068
Closed
6 tasks done
@richardlau richardlau mentioned this pull request Sep 19, 2025
test: skip tests failing when run under root #59779
Merged
@bakkot
bakkot deleted the disposable-mkdtemp branch March 29, 2026 02:52
@gengjiawen gengjiawen mentioned this pull request Oct 8, 2026
fs.mkdtempDisposableSync() / fsPromises.mkdtempDisposable() throw and leak the directory when the path is a Buffer #66593
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@Renegade334 Renegade334 Renegade334 left review comments
@aduh95 aduh95 aduh95 left review comments
@mcollina mcollina mcollina approved these changes
@jasnell jasnell jasnell approved these changes
@legendecas legendecas legendecas approved these changes
@LiviaMedeiros LiviaMedeiros LiviaMedeiros approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

author ready PRs with CI started, the required approvals, and no outstanding review comments. backport-requested-v22.x PRs awaiting manual backport to the v22.x-staging branch. fs Issues and PRs related to file-system APIs and the fs module. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

disposable temporary directory

Uh oh!

There was an error while loading. Please reload this page.

13 participants

@bakkot @jasnell @Renegade334 @aduh95 @himself65 @ShogunPanda @cjihrig @tniessen @legendecas @nodejs-github-bot @mcollina @LiviaMedeiros @richardlau
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.