Skip to content

recommend avoiding async generators, they can easily leak resources #8

@jedwards1211

Description

@jedwards1211

WHATWG web streams have the important property that as soon as the consumer signals they are done by canceling the reader, it promptly communicates that to everything upstream so that everything can be cleaned up right away.

Async generators don't work as well, unfortunately. Even if the consumer .return()s its async iterator as soon as it's done, it may take a long time for the source iterator to find out: if you're passing the stream through an async generator, the async generator has to wait for the next chunk to come through before it can exit its loop and .return() the source iterator.

Well, if the source iterator is wrapping an event stream like a Redis topic, that next event may never come. In this case the async generator is stuck permanently, causing a resource leak, unless the source is aborted via a signal or designed to time out. I dealt with leaks like this in the early days of graphql subscriptions when the libraries didn't support abort signals. I had to convert all my filtering and mapping to avoid async generators to fix the leaks.

I was in love with async iteration at first but I became very disillusioned after this. This is a major weakness in the design, and async iteration no longer feels comfy and nice to me, it feels dangerous.

It's good that you support passing an abort signal to the source stream, but there's no guarantee users will structure things in a way that allows them to abort the source stream when they're done iterating something downstream, so there's a good chance some people wrapping event streams will accidentally suffer resource leaks like this.

So I would recommend warning about this very, very clearly in the documentation. These leaks are completely unexpected, hard to debug, and hard to wrap your head around.

The advice unfortunately needs to be: avoid async generators unless you know the source stream will error or time out if it never receives another chunk, and you're okay with waiting that long to clean up, even if the consumer is already done.

Btw, async iterating WHATWG streams has this same problem. Only canceling them is fully safe.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions