Skip to content

Spring Cloud Gateway MVC leaks connection using Server-Sent-Events #3999

@riccardobellini

Description

@riccardobellini

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;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions