-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Describe the bug
I'm using Spring Cloud Gateway MVC version 5.0.0. I'm encountering a connection leak when requests are made against a service that publishes Server-Sent-Events. This leak is confirmed by checking with netstat the list of established connections.
If I open a connection to the gateway with a standard HTTP client I start receiving the events. If then, after some time, I disconnect from the client, the connection on the gateway remains open, and the downstream service never stops logging sent events.
Note that if I open the connection directly from the HTTP client to the service, instead, and later disconnect, I get an immediate error in logs and no connection is leftover.
Also note that this behavior does not occur when using the reactive version of the gateway. In that case, there is no connection leak.
Is this some kind of still unsupported scenario for the MVC version, such as with Websocket?
Sample
A complete example repo to reproduce this problem is available here:
https://github.com/riccardobellini/spring-cloud-gateway-webmvc-sse-issue
These are the most relevant parts:
I have a route to a downstream service like the one below:
@Bean
public RouterFunction<ServerResponse> apiRoute() {
return route("sse")
.route(path("/stream-sse-mvc"),
HandlerFunctions.http())
.before(uri(URI.create("http://localhost:8781")))
.build();
}The application that receives the request has an endpoint that returns an SseEmitter with no configured timeout, that keeps sending data each second:
@GetMapping("/stream-sse-mvc")
public SseEmitter streamSseMvc() {
SseEmitter emitter = new SseEmitter(-1L);
ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
emitter.onCompletion(() -> {
log.info("Emitter has been closed");
});
emitter.onTimeout(() -> {
log.info("Emitter has timed out");
});
emitter.onError(t -> {
log.error("Emitter completed with error", t);
});
sseMvcExecutor.submit(() -> {
for (int i = 0; true; i++) {
final UUID id = UUID.randomUUID();
SseEmitter.SseEventBuilder event = SseEmitter.event()
.data(new EventDetails(id, Instant.now(), i + 1))
.id(id.toString())
.name("sse event - mvc");
log.info("Sending event. [counter={}]", i);
emitter.send(event);
Thread.sleep(1000);
}
});
return emitter;
}