razshare / sveltekit-sse

Server Sent Events with SvelteKit
https://www.npmjs.com/package/sveltekit-sse
MIT License
307 stars 9 forks source link

Does this library solve the issue of connection closure? #53

Closed t1u1 closed 3 months ago

t1u1 commented 4 months ago

I see that you have logged this issue: https://github.com/sveltejs/kit/issues/11751

Have you managed to workaround this problem in your library? I see that you are sending ping events, but that doesn't seem to help close the connection on the server side.

razshare commented 4 months ago

Hello @t1u1 , Yes, I have found 2 ways, though neither are perfect, the current one I'm using is superior to the first one.

The first solution involved beacon requests , which I dropped for several reasons, one being it has some unnecessary overhead for the client mostly, but also for the server a bit.

The last commit before I dropped this solution is https://github.com/razshare/sveltekit-sse/tree/5a78edf75dd7b92571835535da751c3b31b667e5 you can clone that commit state and take a look around if you want to, idk if it's worth the time, the second solution is better imo.

The second solution is much more interesting.

... and has smaller overhead.

It looks to me that enqueue exceptions are not that well documented.

image

Although it doesn't look like it at first, this is very ambiguous, and contradictory to some extent.

It says it can throw if the object is not a ReadableStreamDefaultController... but the documentation page is for ReadableStreamDefaultController.

Idk what's going on with that documentation but it took me some time to find out that's wrong.

It turns out that the internal check for object is not a ReadableStreamDefaultController is calculated like this - https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue

Which is miles away from what the MDN docs say.

Specifically this part

image

It actually checks if the stream is readable, it seems odd since we're sending stuff down the stream, not reading from it, but I guess it makes sense that you shouldn't send things down a stream that is not readable anymore.

Calling enqueue on a stream that's not connected to a client seems to trigger that check.

So it throws, but it also closes the stream, it actually triggers cancel().

With that in mind, all I have to do is enqueue a ping down the stream to the client every now and then.\ If the enqueue fails, then I can assume the client has disconnected.

https://github.com/razshare/sveltekit-sse/blob/4d526d2d5d509325882672d23904f068b199c132/src/lib/produce.js#L88-L93 https://github.com/razshare/sveltekit-sse/blob/4d526d2d5d509325882672d23904f068b199c132/src/lib/produce.js#L132-L134

Now, idk exactly since this has started to happen, but it looks like the cancel() event now also triggers automatically when the client disconnects.

Idk if that was a SvelteKit bug and they fixed it or anything like that, but I haven't actually tested to see if it's consistent, so I can't say much on that.

From what I've seen at a glance it doesn't seem to be completely bulletproof, so I didn't even look too much into it and kept the ping mechanism as a failsafe.

Peek 2024-07-25 22-40

t1u1 commented 4 months ago

Thanks for the detailed explanation.

However, whenever I try this library or by directly implementing SSE, the connection closure is not detected, and the "stop" handler is not called.

I have tried with firefox and chromium. I have tried setting the ping interval to 4s. Tried with GET and POST requests.

The server never calls the handler upon client disconnection.

svelte-kit : 2.5.18 node: 20.16.0 Linux 6.5.0 vite: 5.3.3 and 5.3.5

Can you please tell me the setup for which the disconnection detection works? And how do you disconnect? I have tried closing the tab itself as well as navigating away from the page which has the source store.

t1u1 commented 4 months ago

I was able to pin down the issue in my case. I was using port forwarding from a container and accessing the 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. So I guess the problem is somewhere in the port forwarding mechanism.

Edit: To be clear, when using local ports, no workaround was necessary. The cancel callback was always triggered by svelte-kit.

razshare commented 3 months ago

I'm closing this issue since there doesn't seem to be an issue at hand and the original question is answered. Feel free to open a new issue or follow up on this one if you have more questions.