What should happen upon collection with an active `using`?
dead-claudia opened this issue · 5 comments
Specifically, I'm imagining two cases:
// Generator
function *foo() {
using x = y
yield x.foo
}
let g = foo()
g.next()
// `g` dropped without invoking `return`
// Async function
async function foo(signal) {
using x = y
await x.foo(signal)
}
foo(AbortSignal.timeout(5000))
// 5 seconds pass
// `x.foo(signal)` gets aborted, cancels operation, inner state now collectable
// `x.foo(signal)` fails to reject with `signal.reason`
Since the inner state is collectable, should the disposer be called, or should a FinalizationRegistry
be used by resources to catch this? Based on the July meeting, the answer seems to be the latter.
using
has no interactions with GC whatsoever. So if you want something to be collected on GC, you'll need to use a FinalizationRegistry
.
In the async function
example, I would assume that await x.foo(signal)
would throw when signal
aborts, in which case x
will in fact get disposed (just as if you'd written const x = y; try { await x.foo(signal); } finally { x[Symbol.dispose]() }
).
As @bakkot said, using
does not interact with GC, so in the generator case, dispose would not be triggered.
In the async function
case, it is better to reject the promise when the operation aborts rather then to just drop the promise on the floor. If that practice is followed, the the using
would properly be triggered.
using
has no interactions with GC whatsoever. So if you want something to be collected on GC, you'll need to use aFinalizationRegistry
.In the
async function
example, I would assume thatawait x.foo(signal)
would throw whensignal
aborts, in which casex
will in fact get disposed (just as if you'd writtenconst x = y; try { await x.foo(signal); } finally { x[Symbol.dispose]() }
).
@bakkot @rbuckton In the async function bit, I was talking about the case where x.foo
drops its resolvers on the floor rather than rejecting like it should. Since the outer promise is discarded immediately, the only reference to that continuation is through the resolvers, and so dropping the resolvers would make it collectable if it's not settled first.
This not resulting in a @@dispose
/@@asyncDispose
call is what I expected after reading the July meeting notes. I was just wanting to make sure this nuance was also addressed.
// Generator
function *foo() {
using x = y
yield x.foo
}
Almost the same as the next step.
function *foo(){
const x = y;
try {
yield x.foo;
} finally {
x[Symbol.dispose]();
}
}
Isn't the problem not so much a using
problem as a problem of generator
's finally
not being processed?
@juner Yes. That's pretty much it. Closed the issue since that and await
ed promises never resolving are basically just two sides of the same coin and the decision is just to let the using
resource also be dropped on the floor without proper cleanup (beyond what any applicable FinalizationRegistry
is wired up to do for it).