-
-
Notifications
You must be signed in to change notification settings - Fork 32k
fs: add disposable mkdtemp #58516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fs: add disposable mkdtemp #58516
Conversation
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 you'd prefer a new |
Let's see if we can get more folks to comment on it before going through the trouble to change it. @nodejs/collaborators |
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.
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. |
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. |
Re: design language for single-use disposables, I think that for almost all cases (in node or elsewhere) I'd recommend the following:
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, Unfortunately |
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,
As for this particular method, I think having an awkwardly named |
I personally think dispoable could be a utility, similar with |
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 Also every time I have to use |
I love the idea. |
@himself65 I also thought that '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-');
} |
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 |
OK, switched to I'll get tests up soon exercising at least
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). |
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 |
Tests added and I believe I've addressed all comments except @legendecas's concern about anonymous objects; PTAL. Testing error propagation requires making the Regarding anonymous objects, I'm happy to switch to using a named interface in the docs but will need someone to suggest what that should look like - would that be nested under the Also, not sure how to address the test failure:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #58516 +/- ##
==========================================
- Coverage 90.23% 90.10% -0.13%
==========================================
Files 635 640 +5
Lines 187632 188448 +816
Branches 36857 36948 +91
==========================================
+ Hits 169303 169798 +495
- Misses 11094 11363 +269
- Partials 7235 7287 +52
🚀 New features to boost your workflow:
|
Thanks! I feel like the new doc looks better and alleviates the concern around anonymous interfaces. Adding them to the FS Common Objects section could be a good option.
A test needs to be added like node/test/fixtures/permission/fs-write.js Lines 202 to 213 in 5584cc5
|
Tried to add the permissions test. Turns out that the async version fails with a weird error (it ends up with an uncaught error despite being wrapped). Then I tried to debug it and it turns out that So there's a pre-existing issue which I just exposed by adding a new test. I have no idea how to go about fixing that error and in any case it should presumably not happen in this PR. I've created #58747 for the existing bug. Either it needs to get fixed to unblock this or I can remove the async test for now. It does work in the sense of correctly failing to create the directory, it's just that the internal error handling logic is messed up in a way which prevents testing it properly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
I think there are some releated failures.
I just removed the failing test for now, since it's a pre-existing issue. Can re-land it with a fix to #58747 if anyone figures out what's going on there. |
* Returns: {Promise} Fulfills with a Promise for an async-disposable Object: | ||
* `path` {string} The path of the created directory. | ||
* `remove` {AsyncFunction} A function which removes the created directory. | ||
* `[Symbol.asyncDispose]` {AsyncFunction} The same as `remove`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a pattern here like what we do with the various pseudo-classes for params/options cases in Web Crypto. https://nodejs.org/docs/latest/api/webcrypto.html#algorithm-parameters ... these aren't actual classes in the code but are documented as such to improve navigation and documentability in the docs. Essentially, while this new method is actually returning an anonymous object, it can still be documented as if it were a named class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do this, but where should the docs go? fs
has a "Common Objects" section but that says
The common objects are shared by all of the file system API variants (promise, callback, and synchronous).
which is not true of these objects - the sync and async versions return objects with a different shape, specific to that function and no other. My inclination would be to make a sub-heading within the fs.mkdtempDisposableSync
and fsPromises.mkdtempDisposable
sections, but that's not really how any of the other docs look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just put them there in the Common Objects
section. We can shift things around later if necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But really it's up to you. Wherever you feel most comfortable with it.
For detailed information, see the documentation of [`fsPromises.mkdtemp()`][]. | ||
The optional `options` argument can be a string specifying an encoding, or an | ||
object with an `encoding` property specifying the character encoding to use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This really should be expanded to indicate what the encoding is used for. This might be an existing problem in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I'd prefer if @jasnell's comment at #58516 (comment) can be incorporated as well.
This reverts commit 8dc0d54.
Turns out I don't know how to add classes to the docs. I tried but the build failed with an error I don't know how to fix (I'm guessing I'd have to edit I'm personally happy with the current state with anonymous objects in the docs. If someone who prefers named interfaces wants to go through the effort of updating the docs correctly, go for it; you should be able to push to this branch. Otherwise I'll leave it as is. |
That's fine too I think. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Co-authored-by: Livia Medeiros <[email protected]>
Adds
fs.mkdtempDisposableSync
andfs.promises.mkdtempDisposable
, which work likemkdTemp
except that they return a disposable object which removes the directory on disposal.Fixes #58486.