0

I'm trying to establish an SSE communication channel between a Jakarta EE server and other web or Java clients. In order to send messages to such clients, I keep a list of them as internal server state. The problem is, I can't find a way to detect when the clients close the connection, so that I can free up the corresponding server resources.

Here is more or less how I am establishing the connection:

@ApplicationScoped
@Path("/sse")
public class SseController {

    private Sse sse;
    private List<SseEventSink> sinks = new CopyOnWriteArrayList<>();

    @GET
    @Produces(SERVER_SENT_EVENTS)
    public void onSse(@Context Sse sse, @Context SseEventSink sink) {
        this.sse = sse;
        this.sinks.add(sink);
    }
}

When is one supposed to clean up a closed client? From my tests, the server remains unaware of a client closure for as long as I've been willing to wait (~15 minutes), unless the server attempts to send three messages to the closed client; then somehow, after three send attempts, the third one fails, which gives an opportunity for cleaning up. I find this far from optimal.

If it helps, I am using OpenLiberty, which I believe uses RESTEasy as its SSE implementation.

DanielM
  • 1,106
  • 3
  • 17
  • 27
  • What do you do with the sinks collection? – James R. Perkins Jun 23 '22 at 00:11
  • I later iterate through them and selectively send them messages (depending on the type of message each client/sink is interested on). – DanielM Jun 23 '22 at 07:24
  • That seems like an odd use-case TBH. You could close them when the `SseController` is destroyed with a `@PreDestroy`, but it doesn't really sound like what you're looking for. – James R. Perkins Jun 23 '22 at 19:17
  • I can't see what's odd about sending messages to SSE clients... And no, you are right, `@PreDestroy` is not what I'm after. – DanielM Jun 27 '22 at 08:46
  • What seems strange about it to me is collecting the `SseEventSink`. It's designed to be used with try-with-resources and disposed quickly. – James R. Perkins Jun 27 '22 at 15:14
  • Seriously? The server receives events and wants to relay them to some (or even all) clients. How's that not a use case considered in the design? That'd be baffling. I swear, Java sometimes... – DanielM Jun 28 '22 at 12:06
  • I think you'd want to use a [`SseBroadcaster`](https://jakarta.ee/specifications/restful-ws/3.1/jakarta-restful-ws-spec-3.1.html#sse_broadcasting) for that. – James R. Perkins Jun 28 '22 at 14:58
  • Except in my case I do want to be selective, as not all clients should receive all messages. In any case, for lack of better options, I indeed tried using `SseBroadcaster`, seeing that it has an `onClose()` callback. Unfortunately, it has the same limitation: a call to `SseEventSource.close()` by an SSE client does not trigger this callback, so the broadcaster continues to attempt sending messages long after the client has said bye-bye. It is only after three failed attempts that `onClose()` is called; and unless that happens, the clients keep accumulating in memory. – DanielM Jun 28 '22 at 16:53
  • I'll see if I can create a reproducer. IIRC I've seen something similar with socket writes as there is no real way to know that client has been closed. – James R. Perkins Jun 28 '22 at 21:03
  • Regarding there not being a "real way to know that client has been closed", with the recent TCP standard consolidation into a single document, it might a good time to review this assumption. It seems clear to me that the standard does consider a sequence for peers to indicate they want to close a connection: https://www.rfc-editor.org/rfc/rfc9293#name-closing-a-connection – DanielM Aug 20 '22 at 18:07
  • Sorry, I didn't mean by spec. I meant with socets/io/nio in Java. – James R. Perkins Aug 22 '22 at 15:25

1 Answers1

0

You should be able to check if the stream has been closed via the isClosed() method documented in the javadoc for SseEventSink.

  • Unfortunately, that is not the case. After a client closes the connection (i.e. there is a call to `SseEventSource.close()`, after which `SseEventSource.isOpen()` correctly returns false), the server doesn't see that the connection has been closed (i.e. `SseEventSink.isClosed()` returns false). It is only after the server attempts and fails to send three messages to a closed client that `SseEventSink.send()` will return an exception, and from that point on `SseEventSink.isClosed()` correctly returns true. – DanielM Jun 23 '22 at 07:30