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

Canceled Response #11751

Closed
razshare opened this issue Jan 28, 2024 · 6 comments
Closed

Canceled Response #11751

razshare opened this issue Jan 28, 2024 · 6 comments

Comments

@razshare
Copy link

razshare commented Jan 28, 2024

Describe the bug

When dealing with streams or large amount of data in general, I need a way to detect when a client has disconnected, otherwise I'll be wasting I/O time on emitting data to a stream that nobody's ever going to read.

Here's an example

export function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      for (const chunk of someLargeAmountOfData) {
        controller.enqueue(chunk)
      }
      controller.close()
    }
  })

  return new Response(stream, {
    headers: {
      'Cache-Control': 'no-store',
      'Content-Type': 'application/octet-stream',
      'Connection': 'keep-alive',
    },
  })
}

That readable stream will continue to enqueue data until it's done, even if the client has aborted the connection (e.g. closed the tab).

From our side, in user-land, there's currently no way to detect when a client has disconnected, and it looks like the cancel event on the readable stream is also not triggered, so this also doesn't work:

export function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      for (const chunk of someLargeAmountOfData) {
        controller.enqueue(chunk)
      }
      controller.close()
    },
    cancel(){ // this doesn't work
      manageCancelation()
    }
  })
  
  return new Response(stream, {
    headers: {
      'Cache-Control': 'no-store',
      'Content-Type': 'application/octet-stream',
      'Connection': 'keep-alive',
    },
  })
}

Reproduction

https://github.com/tncrazvan/sveltekit-canceled-response-issue-reproduction

Logs

No response

System Info

System:
    OS: Linux 6.5 Zorin OS 17 17
    CPU: (32) x64 AMD Ryzen 9 5950X 16-Core Processor
    Memory: 27.18 GB / 31.23 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
    npm: 10.2.4 - ~/.nvm/versions/node/v20.11.0/bin/npm
    bun: 1.0.25 - ~/.bun/bin/bun

Severity

blocking an upgrade

Additional Information

When the content of the response is indeed a ReadableStream and the client aborts the http connection, sveltekit should invoke the cancel method on the stream.

If you point me into the right direction, I'm happy to give it a shot a fixing this myself, I just need some general indications.

@t1u1
Copy link

t1u1 commented Jul 25, 2024

If ReadableStream is used for SSE, then this is even more problematic, because the stream never closes, as there is no way to know whether the client has disconnected. If for example, an implementation keeps enqueuing status data to the controller in a interval timer, there is no end to the timer nor the data!

@t1u1
Copy link

t1u1 commented Jul 25, 2024

Perhaps this will be useful for debugging:

The request object's signal is not aborted after closing the connection.

console.log(request.signal.aborted) always prints false.

@t1u1
Copy link

t1u1 commented Jul 25, 2024

With node-adapter this gets even worse, I am not able to kill the server with the SIGTERM signal (ctrl-C on the shell) when an SSE connection is initiated! It requires a SIGKILL before it can be terminated.

@t1u1
Copy link

t1u1 commented Jul 26, 2024

I was able to pin down the issue in my case. I was using port forwarding from a container and accessing the SSE API through the forwarded port. In this case the client side closure doesn't trigger the closure on the server side.

However, if I curl from the remote host's shell to the server's local port, then closing the connection by killing curl works perfectly and the stream's cancel handler is called. So I guess the problem is somewhere in the port forwarding mechanism.

@razshare
Copy link
Author

razshare commented Oct 6, 2024

A quick update, the cancel handler seems to trigger fine in NodeJs.
The issue seems to be just with Bun now, see razshare/sveltekit-sse#58 (comment)

I'm starting to think it's an upstream issue with Bun itself, since I don't see any Kit changes referenced in this issue regarding this cancel handler.

@eltigerchino
Copy link
Member

I've tested this and it does indeed cancel after a few seconds of disconnection (refreshing the page, navigating away from the page, etc.). I'll close this issue for now

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

No branches or pull requests

3 participants