tc39 / proposal-explicit-resource-management

ECMAScript Explicit Resource Management

Home Page:https://arai-a.github.io/ecma262-compare/?pr=3000

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

What should happen upon collection with an active `using`?

dead-claudia opened this issue · 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 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]() }).

@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 awaited 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).