oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
73.02k stars 2.66k forks source link

ReadableStream cancel is not called when client disconnects #6758

Closed luccas40 closed 6 days ago

luccas40 commented 10 months ago

What version of Bun is running?

1.0.7

What platform is your computer?

Debian Bullseye x64

What steps can reproduce the bug?

Running Bun with Svelte, in a API GET function:

    let keepAlive = true;
    const stream = new ReadableStream({
        start: async controller =>{
            while(keepAlive){           
                await new Promise(r => setTimeout(r, 1000));
            }
        },
        cancel: () =>{
            console.log("cancelled");
            keepAlive = false;
        }
    });
    return new Response(stream, {
        headers: {
            "Cache-Control": "no-store",
            "Content-Type": "text/event-stream",
            "Connection": "keep-alive",
        }
    });

What is the expected behavior?

Everytime the page is closed or the connection is closed by the client we should see a log on the server "cancelled".

What do you see instead?

Nothing is logged and the loop keeps running even if the page is fully closed.

Additional information

It works just fine with Node

giesf commented 9 months ago

Any news on this? :/

ksmithut commented 6 months ago

@giesf I'm not exactly sure how it's supposed to work, but I got something working going off of the request abort signal rather than relying on the response cancel callback:

const server = Bun.serve({
  async fetch(req) {
    const stream = new ReadableStream({
      async start(controller) {
        const encoder = new TextEncoder()
        let count = 0
        const interval = setInterval(() => {
          controller.enqueue(encoder.encode(`${count++}\n`))
        }, 1000)
        let closed = false
        function close() {
          if (closed) return
          console.log('aborted')
          closed = true
          clearInterval(interval)
          req.signal.removeEventListener('abort', close)
          controller.close()
        }
        req.signal.addEventListener('abort', close)
        if (req.signal.aborted) return close()
      }
    })
    return new Response(stream)
  }
})

Now running curl localhost:3000 will get a stream of numbers, and when you ctrl + c you get the aborted log and the request artifacts get cleaned up.

Is that what you're looking for?

TheArmagan commented 5 months ago

Any updates?

pekeler commented 3 months ago

We're using SSE for the "realtime" aspect of our SPA. Once these ReadableStreams are used up, the app freezes (because server event listeners don't get unregistered). So this isn't just a blocker for going to prod, but it's making development super annoying. Can we please have an ETA for a bug fix?

Jarred-Sumner commented 6 days ago

This will be fixed in Bun v1.1.27