Skip to content

Consider removing the autoclose option from createEventStream #1489

Description

@pi0x

Follow-up from #1478 / #1484 / #1488. Now that onDispose (#1488) gives EventStream a portable end-of-event signal, the autoclose option looks vestigial — and its false branch is arguably harmful. Raising it before v2 stable, while removing it is still free.

What it does today

EventStreamOptions.autoclose (default true) gates two things in src/utils/internal/event-stream.ts:

  1. L42-47 — registers onDispose(event, () => this.close()), i.e. auto-close when the event ends.
  2. L35-37 — whether _disposed is set inside the writer-closed handler.

Effect 2 is nearly unobservable: _writerIsClosed is set outside the gate on L34, and _isClosed is _writerIsClosed || _disposed, so push/pushComment/flush no-op either way. The only difference is whether a later manual close() early-returns or falls through to a no-op branch.

Why autoclose: false may no longer be meaningful

  • It cannot keep the stream writable after a disconnect. Cancelling the readable errors the writable side regardless of the option, so pushes no-op either way. The transport is gone; no flag brings it back.

  • It cannot cause a premature close. For a stream that is the response body, onDispose fires only once the body finishes streaming — at which point close() is a no-op. So autoclose: true can never cut a live stream short, which was the original reason to offer an opt-out.

  • It does not control onClosed delivery for a sent stream; those callbacks fire from the writer-closed handler independently.

  • It silently disables cleanup for unsent streams. A stream created but never send()-ed with autoclose: false never fires onClosed at all — and nothing the user can do recovers it, because the response is already gone and there is no other signal that the event ended. Verified:

    scenario onClosed runs
    unsent, handler returns normally yes
    unsent, handler throws after registering yes
    sent stream, normal completion yes
    unsent, autoclose: false no

So the remaining behavioral surface of autoclose: false is: h3 does not call close() for you at end of event — in the one case where that matters, it produces a leak.

Current coverage and exposure

  • Not referenced anywhere in docs/ — only the JSDoc on the type.
  • No test passes autoclose: false. The opt-out branch is entirely uncovered (test/sse.test.ts:163 is named "autocloses…" but exercises the default).

Historical note

The option made sense when introduced in #586 (Feb 2024): h3 v1 was Node-only, event.node.req.on("close") was the only lifecycle signal available, and opting out of a Node listener was a coherent choice. The v2 rewrite and #1488 replaced that mechanism entirely; the option outlived its mechanism.

Options

  1. Remove it. Cheapest before v2 stable. EventStream always registers onDispose.
  2. Keep the type, ignore the value, with a deprecation notice, and drop it in a later major.
  3. Keep it, but fix the leak — always register onDispose and have the callback respect autoclose (so cleanup/onClosed always runs, while a live stream is still not force-closed). Preserves the opt-out with no dead end.
  4. Keep as-is and document that autoclose: false disables onClosed for streams that are never sent.

I lean 1, with 3 as the fallback if there is a real consumer. Worth checking nitro and other downstreams for autoclose usage before deciding — I only verified this repo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions