-
Notifications
You must be signed in to change notification settings - Fork 29
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
Make disposables composable - Syntax for returning the cleanup safely #55
Comments
Introduce a move operator. function openMultipleResources() {
using const res1 = openResource(1);
/* A */
using const res2 = openResource(2);
/* B */
const result = [>>>res1, >>>res2];
/* C */
return result;
} Semantics: When For transferring resource ownership into closures, maybe something like: () using x, >>>y, z => f(x, y, z) which would then be equivalent to ((x, y, z) => {
const closure = () => f(x, y, z);
closure[Symbol.dispose] = () => {
using const _x = x;
/* y skipped */
using const _z = z;
};
return closure;
})(>>>x, >>>y, >>>z) And then disposing should be recursive, so that this works: function makeObjectWithStuff() {
using const res1 = openResource(1);
using const res2 = openResource(2);
/* if an exception is thrown here, both are disposed */
return {
/* if execution reaches the evaluation of the object constructor,
* ownership is transferred to the constructed object
*/
foo() {
use(res1, res2)
},
[Symbol.dispose]: () using res1, res2 => {}
}
} |
Hi @rbuckton , the linked PR that closes this. Seems like the “issue” would still apply to the new design? Even with the Thinking about this issue again with 9 months of hindsight. I think what I was trying to get to was having the same mechanism but for For example: using const x = …
holding const y = … gives: let _x, _y;
try {
const x = …;
_x = __getDispose(x);
const y = …;
_y = __getDispose(y);
} catch(e) {
__dispose(_y);
throw e;
} finally {
__dispose(_x);
} When we are “using” something then we are the end of the chain, and any completion should clean up the resource. This could be a follow on proposal. As it seems purely additive. |
For {
using const x = ...;
const y = ...; // thing you want to hold
using const holder = Disposable.from(y);
// do work that could throw (a)
// stop tracking `y` in the holder
holder.delete(y);
return y;
// if (a) throws, `holder`, `y`, and `x` are disposed
// if (a) does not throw, `holder` and `x` are disposed, but `y` is not because it was removed.
} I'm not sure I want to extend the Another option could be a static method on {
using const x = ...;
const y = ...;
using const holder = Disposable.hold(y);
// do work that could throw
holder.release(); // unenlists `y` from the holder. It will no longer be disposed at the end of the block.
return y;
} In absence of such an API, you could still be accomplish this in userland: {
using const x = ...;
const y = ...;
// holder to track safe completion/cleanup of 'y'
let ok = false;
using const (new Disposable(() => {
if (ok) return;
using const (y);
}));
// do work that could throw
ok = true;
return y;
} |
“Revocable” disposables also came to my mind too. consider me convinced that this can be solved effectively in user land. Thanks @rbuckton! |
I think this is something we can consider in a future/add-on proposal as it is outside the scope of the base functionality, but something like |
Please see #80 where I've proposed introducing a |
For example: // "revoking" a dispose
using const holder = new DisposableStack();
const y = holder.use(...);
...
// stop tracking y
holder.move();
return y;
// opening multiple resources
function openMultipleResources() {
using const stack = new DisposableStack();
const res1 = stack.use(openResource(1));
const res2 = stack.use(openResource(2));
// if we've reached this point, both resources were successfully
// allocated. Move them off the stack so that they aren't
// disposed when the block exits.
stack.move();
return [res1, res2];
} |
Thanks for the update. That looks great! |
TLDR:
The proposal in this repo helps the final 'end of the chain' but it doesn't help the intermediary functions that are creating the resources on behalf of the caller. The resource creation and accompanying cleanup may need to be threaded through multiple layers and still be exception safe.
Background:
A pattern found in many libraries is for resource cleanup to be returned as a function:
To make use of the
using
syntax a similar pattern is required with the difference being that the cleanup function is found by accessing theSymbol.dispose
property on the returned object.Problem:
Returning the clean-up function at the end like this has similar issues as the calling side has; exceptions stop the cleanup.
Calling side issue that
using
addresses:Exception throwing issue on callee side:
Current Solution:
To safely create the resource to pass to the caller the code needs to be re-written using try/catch as below. But this type of code seems to be the type of code this proposal is trying to help code authors move away from.
Ideation:
Could a function declare syntactically that it creates resources that it intends the caller to dispose of, this way the language itself could ensure the resources are closed even if an exception is thrown mid way through. It could also help IDEs suggest when to use
using
.being roughly equivalent to:
For the libraries where the only expected return value is the cleanup, perhaps a
resource function
andresource () =>
would automatically return all the created resources in an aggregated Disposable (#41).Trying to
return
any other value would be a syntax error.Thoughts?
The text was updated successfully, but these errors were encountered: