spring-cloud / spring-cloud/spring-cloud-gateway

Unknown reason triggers .doOnCancel(() -> cleanup(exchange)) in NettyWriteResponseFilter occasionally

Open
#3,301 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feedback-provided
Dominant language
Java
Stars
4.9k
Forks
3.5k
Avg merge
20h 57m
Merged PRs (30d)
8

Description

Describe the bug
Spring Cloud Gateway version:2.2.5
reactor-netty version :0.9.15

  • Use method ServerHttpResponse.writeWith(Mono<DataBuffer> instead of ServerHttpResponse.writeWith(Flux<DataBuffer>) in NettyWriteResponseFilter.java in high-concurrency scenarios triggers doOnCancel(() -> cleanup(connection)) occasionally
  • Method doOnCancel(() -> cleanup(connection)) will close the long connection between the gateway and downstream services, and this closure will not be monitored by the connection pool status, resulting in subsequent requests generating exception reactor.netty.channel.AbortedException: Connection has been closed BEFORE response while sending request body

Describe the solution you'd like

  • Understanding the root cause of mono triggering doOnCancel
  • Connection pool can monitor connection status when the connection is closed

Sample

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    return chain.filter(exchange)
        
        .doOnError(throwable -> cleanup(exchange))
        .then(Mono.defer(() -> {
            
            Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);
            if (connection == null) {
                return Mono.empty();
            }
            ServerHttpResponse response = exchange.getResponse();
            
            final Flux<DataBuffer> body = connection
                .inbound()
                .receive()
                .retain()
                .map(byteBuf -> wrap(byteBuf, response));

            // My changes are here
            Mono<DataBuffer> newBody = body.single();
            MediaType contentType = null;
            try {
                contentType = response.getHeaders().getContentType();
            }
            catch (Exception e) {}
           
            return (isStreamingMediaType(contentType)
                ? response.writeAndFlushWith(body.map(Flux::just))
                : response.writeWith(newBody));
        })).doOnCancel(() -> cleanup(exchange));
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in NettyWriteResponseFilter.java and compare the reported writeWith(Mono) path with the Flux path. Reproduce the high-concurrency scenario and trace when doOnCancel(() -> cleanup(exchange)) runs. Done means the cancellation cause is understood and connection closure is correctly reflected in connection-pool status.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring, spring-boot
Domain
api, backend, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.