Closed spring-projects-issues closed 9 years ago
Konrad Garus commented
Looks like manually calling SseEmitter.complete
is enough to prevent the stray thread from writing:
emitter.onCompletion(emitter::complete);
It seems like something that the library should do by itself.
Rossen Stoyanchev commented
The Servlet API provides callbacks on timeout and Spring MVC exposes them too, e.g.:
emitter.onTimeout(() -> {
emitter.complete();
});
You can also register timeout logic globally. An example with the MVC namespace but the same can be done through the MVC Java config (i.e.WebMvcConfigurer
) too.
To the best of my knowledge you shouldn't have to handle timeouts for the response to be closed. I believe the above behavior where a new request is established and the old response starts writing to it is a Tomcat bug which I've reported https://bz.apache.org/bugzilla/show_bug.cgi?id=58457. Switching to Jetty also shows it's Tomcat specific behavior.
That said we could make some improvements on our side as you suggested. We already have a completed
flag in ResponseBodyEmitter (the base class for SseEmitter) and could set it after timeout. I'll have a look at that.
As for the JSON at the end after the timeout, I've suggested an improvement for Boot's default ErrorController to not write if the response is committed. My comments there.
Rossen Stoyanchev commented
Repro project https://github.com/spring-projects/spring-framework-issues/tree/master/SPR-13498.
Rossen Stoyanchev commented
I've added hooks to detect timeout/completion and set the complete flag. The above code now works as expected even ahead of any potential Tomcat fixes.
Konrad Garus opened SPR-13498 and commented
Let's say I have a misbehaving handler that keeps using SseEmitter even though it's closed/timed out:
When I get the event stream for the first time, it looks like you would expect, except for the JSON at the very bottom (see #18075):
Now, have a look at what happens on reconnect:
We can see both threads writing to the same connection, even though the first emitter was supposedly closed!
What's even more interesting, it's not limited to that endpoint or SSE streams. Here's what happens when I try to load a static resource:
Note the "data:data:data:..." on top.
Yes, the code leading to this bug is broken. It's just a made-up example though. I suspect in the real life such issues are still possible with clean-looking code.
I think this should be addressed by preventing use of a closed
SseEmitter
. If you try to send an event to one, you get an exception immediately and it doesn't even try to touch the underlying connection. Logically something like:Affects: 4.2.1
Issue Links:
18075 Poor SseEmitter handling of timeouts