Skip to content
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

Disposal should be based on a DAG, not a stack (e.g. AsyncDisposableDag) #246

Open
alexweej opened this issue Nov 7, 2024 · 1 comment

Comments

@alexweej
Copy link

alexweej commented Nov 7, 2024

AS A Node.js developer
I WANT to not spend more wall-clock time disposing of async resources than necessary
SO THAT my applications do not feel sluggish compared to other languages

Resources have construction dependencies, which are naturally expressed by having to pass dependencies as arguments, e.g.

const mySession = await ThingySession.create();
const myFrobnicatorService = await FrobnicatorService.create(mySession);

When disposing of these resources, it's reasonable but not correct to assume that there is a LIFO destruction order requirement. In theory, if you know that myFrobnicatorService does not retain a reference to mySession, you can dispose of both resources in parallel, without waiting for myFrobnicatorService to dispose first before starting to dispose of mySession.

This issue naturally extends to more complex DAGs of dependency relationships (equivalently, lifetime constraints). Crucially, you cannot in general infer anything about the optimal disposal plan.

Just as a starting point for a solution, perhaps having a protocol for a resource[Symbol.resourceDependencies] property which returns a Set of resources, can give us the information we need to dispose of things with the shortest critical path, i.e. disposing of resources as soon as they no longer have dependents. If this [Symbol.disposeDependencies] property is undefined, then fall back to LIFO disposal.

Thanks

@dead-claudia
Copy link

How many things are you tearing down? Also, keep in mind, other languages with deterministic destructors like C++ and Rust also do it sequentially, and Rust's own AsyncDrop proposal is sequential.

A AsyncDisposableGroup type could solve this, but IMHO it's worth punting to a follow-on (I'm not TC39, so not my decision).

class AsyncDisposableGroup {
    #items = []
    constructor(items) {
        if (items) this.#items = [...items]
    }
    use(item) {
        this.#items.push(item)
        return item
    }
    async [Symbol.asyncDispose]() {
        await Promise.all(this.#items.map(async i => i[Symbol.asyncDispose]()))
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants