Skip to content

JettyClientHttpConnector buffer leak in Spring Framework 6.2 #35319

@Kerry-G

Description

@Kerry-G

Spring Framework 6.2 introduces a buffer leak when using JettyClientHttpConnector with a Jetty's httpClient. This worked correctly in Spring Framework 6.1.

We tested this against all 6.2.x version and 6.1.x version. Always produce leaky buffers on 6.2.x. and it is always fine with versions 6.1.x.

In this example, we use ArrayByteBufferPool.Tracking to make it obvious there is a buffer leak, but in our actual project, we use the default implementation of ArrayByteBufferPool.

Here's a OneDrive link to an actual project but also the code here if you don't want to download external files:

# .\spring-memory-leak-test\src\main\java\com\test\spring\WebClientBuilderStoreExecutor.java
package com.test.spring;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ArrayByteBufferPool.Tracking;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class WebClientBuilderStoreExecutor {
    public static void main(String[] args) throws Exception {
        ClientConnector clientConnector = new ClientConnector();

        HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));

        Tracking tracking = new Tracking();

        httpClient.setByteBufferPool(tracking);

        WebClient client =  WebClient.builder()
                        .clientConnector(new JettyClientHttpConnector(httpClient))
                        .build();


        Map<Integer, String> results = new ConcurrentHashMap<>();
        ExecutorService executor = Executors.newFixedThreadPool(50);

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.submit(
                    () -> {
                        String block =
                                client.get()
                                        .uri("http://httpbin.org/anything")
                                        .retrieve()
                                        .bodyToMono(String.class)
                                        .block();
                        results.put(index, block);
                    });
        }

        executor.shutdown();
        executor.awaitTermination(60, TimeUnit.SECONDS);

        System.out.println(
                "Completed request. Results stored in HashMap with "
                        + results.size()
                        + " entries.");
        System.out.println("Leaks count: " + tracking.getLeaks().size());
        tracking.getLeaks().forEach(leaks -> System.out.println("Leaked buffers: " + leaks));
    }
}
# \spring-memory-leak-test\pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.test.spring</groupId>
	<artifactId>spring-leak-test</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name></name>
	<url>http://maven.apache.org</url>
	<properties>
		<jetty-reactive-httpclient.version>4.0.10</jetty-reactive-httpclient.version>
		<!-- <spring-boot.version>3.5.4</spring-boot.version>
      	<spring.version>6.2.9</spring.version> -->
		<spring-boot.version>3.3.12</spring-boot.version>
      	<spring.version>6.1.20</spring.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.eclipse.jetty</groupId>
			<artifactId>jetty-reactive-httpclient</artifactId>
			<version>${jetty-reactive-httpclient.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
			<version>${spring-boot.version}</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>17</source>
					<target>17</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<configuration>
					<archive>
						<manifest>
							<addClasspath>true</addClasspath>
							<classpathPrefix>lib/</classpathPrefix>
							<mainClass>com.test.spring.WebClientBuilderStore</mainClass>
						</manifest>
					</archive>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-assembly-plugin</artifactId>
				<configuration>
					<archive>
						<manifest>
							<mainClass>com.test.spring.WebClientBuilderStore</mainClass>
						</manifest>
					</archive>
					<descriptorRefs>
						<descriptorRef>jar-with-dependencies</descriptorRef>
					</descriptorRefs>
				</configuration>
				<executions>
					<execution>
						<id>make-assembly</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions