Skip to content

Commit 764336f

Browse files
committed
Fix Jetty DataBufferFactory memory leak
Prior to this commit, gh-32097 added native support for Jetty for both client and server integrations. The `JettyDataBufferFactory` was promoted as a first class citizen, extracted from a private class in the client support. To accomodate with server-side requirements, an extra `buffer.retain()` call was performed. While this is useful for server-side support, this introduced a bug in the data buffer factory, as wrapping an existing chunk means that this chunk is already retained. This commit fixes the buffer factory implementation and moved existing tests from mocks to actual pooled buffer implementations from Jetty. The extra `buffer.retain()` is now done from the server support, right before wrapping the buffer. Fixes gh-35319
1 parent 4903fee commit 764336f

File tree

3 files changed

+22
-15
lines changed

3 files changed

+22
-15
lines changed

spring-core/src/main/java/org/springframework/core/io/buffer/JettyDataBuffer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ public final class JettyDataBuffer implements PooledDataBuffer {
5555
this.bufferFactory = bufferFactory;
5656
this.delegate = delegate;
5757
this.chunk = chunk;
58-
this.chunk.retain();
5958
}
6059

6160
JettyDataBuffer(JettyDataBufferFactory bufferFactory, DefaultDataBuffer delegate) {

spring-core/src/test/java/org/springframework/core/io/buffer/JettyDataBufferTests.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,47 @@
1818

1919
import java.nio.ByteBuffer;
2020

21+
import org.eclipse.jetty.io.ArrayByteBufferPool;
2122
import org.eclipse.jetty.io.Content;
23+
import org.eclipse.jetty.io.RetainableByteBuffer;
24+
import org.junit.jupiter.api.AfterEach;
2225
import org.junit.jupiter.api.Test;
2326

2427
import static org.assertj.core.api.Assertions.assertThat;
2528
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
26-
import static org.mockito.BDDMockito.given;
27-
import static org.mockito.BDDMockito.then;
28-
import static org.mockito.Mockito.mock;
29-
import static org.mockito.Mockito.times;
3029

3130
/**
31+
* Tests for {@link JettyDataBuffer}
3232
* @author Arjen Poutsma
33+
* @author Brian Clozel
3334
*/
3435
public class JettyDataBufferTests {
3536

3637
private final JettyDataBufferFactory dataBufferFactory = new JettyDataBufferFactory();
3738

39+
private ArrayByteBufferPool.Tracking byteBufferPool = new ArrayByteBufferPool.Tracking();
40+
3841
@Test
3942
void releaseRetainChunk() {
40-
ByteBuffer buffer = ByteBuffer.allocate(3);
41-
Content.Chunk mockChunk = mock();
42-
given(mockChunk.getByteBuffer()).willReturn(buffer);
43-
given(mockChunk.release()).willReturn(false, false, true);
44-
43+
RetainableByteBuffer retainableBuffer = byteBufferPool.acquire(3, false);
44+
ByteBuffer buffer = retainableBuffer.getByteBuffer();
45+
buffer.position(0).limit(1);
46+
Content.Chunk chunk = Content.Chunk.asChunk(buffer, false, retainableBuffer);
4547

46-
47-
JettyDataBuffer dataBuffer = this.dataBufferFactory.wrap(mockChunk);
48+
JettyDataBuffer dataBuffer = this.dataBufferFactory.wrap(chunk);
4849
dataBuffer.retain();
4950
dataBuffer.retain();
5051
assertThat(dataBuffer.release()).isFalse();
5152
assertThat(dataBuffer.release()).isFalse();
5253
assertThat(dataBuffer.release()).isTrue();
5354

5455
assertThatIllegalStateException().isThrownBy(dataBuffer::release);
56+
assertThat(retainableBuffer.isRetained()).isFalse();
57+
assertThat(byteBufferPool.getLeaks()).isEmpty();
58+
}
5559

56-
then(mockChunk).should(times(3)).retain();
57-
then(mockChunk).should(times(3)).release();
60+
@AfterEach
61+
public void tearDown() throws Exception {
62+
this.byteBufferPool.clear();
5863
}
5964
}

spring-web/src/main/java/org/springframework/http/server/reactive/JettyCoreServerHttpRequest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ public Flux<DataBuffer> getBody() {
115115
// We access the request body as a Flow.Publisher, which is wrapped as an org.reactivestreams.Publisher and
116116
// then wrapped as a Flux.
117117
return Flux.from(FlowAdapters.toPublisher(Content.Source.asPublisher(this.request)))
118-
.map(this.dataBufferFactory::wrap);
118+
.map(chunk -> {
119+
chunk.retain();
120+
return this.dataBufferFactory.wrap(chunk);
121+
});
119122
}
120123

121124
}

0 commit comments

Comments
 (0)