How to cleanup resources associated with EventSource (SSE) endpoints?

Network.Wai.EventSource from wai-extra provides a simple api to allow sending Server-Sent Events (SSE) to clients.
You just provide a Chan ServerEvent to eventSourceAppChan and anything you write to this Chan is sent to the client that connects to the underlying endpoint (let’s call it SSE endpoint).

Now suppose I want ho have one Chan ServerEvent for each user (identified by UserId) and I want to be able to send SSE events through it from other, normal HTTP handlers.
I can stick a Map UserId (Chan ServerEvent) in some mutable variable inside my App’s ReaderT environment.
If user connects to SSE endpoint, I insert a new Channel ServerEvent under their UserId into the Map.
Any time I want to send something to that client from other HTTP handlers, I just lookup the Chan in the map and send message to it via writeChan usersChan someMsg.

Here’s a standalone gist with the source code extracted from larger App, stripped from all unnecessary complexity:

… and here’s a screenshot of example interactions

Now the problem I have is that with growing number of listening users, the number of Chans in the Map grows.
Question: Ideally I’d like to know when given UserId has no listening connections and remove that user’s Chan from the Map. But I didn’t figure out a way to detect whether given SSE client disappeared (e.g. because they closed their browser). Is there a way to do this?

I searched all the usage of eventSourceAppChan on github,
but most of them either just create a single Chan for the whole app, or they just accumulate Chans somewhere without ever cleaning them up.

Is using Map UserId (Chan ServerEvent) reasonable idea to share state between handlers? Could it be made to work?

I saw the yesod seems to have some support for this, but I’m using Servant and would like to stick with it.

Please note I’m aware that WebSockets exist, but I’m interested if there’s a way to solve this via EventSource.

2 Likes

Hi, I don’t have any Haskell code unfortunately but just wanted to point out that:

“You could use the IsClientConnected property of the HttpResponse class to detect client disconnection.”

I don’t think there is support for that in the lib so maybe it’s worth extending it?

1 Like

I thought a bit about your problem. Unfortunately any idea I get about user session management is heavily influenced by IRC, where clients regularly ping the server even in the absence of server-sent data, and for this you need bidirectional connection. HTMX has a way to trigger a callback to the server upon receiving an SSE, but that’s predicated on receiving anything from the server in the first place.

So yeah, maybe websockets are a more appropriate foundation for your needs.