Skip to content

Commit 912c067

Browse files
committed
Fix buffer leak in Jackson2 decoders
Prior to this commit, the Jackson2 decoders (JSON, Smile, CBOR) could leak buffers in case the decoding operation times out or is cancelled and some buffers are still in flight. This commit ensures that buffers are released on cancel signals. Fixes gh-33731
1 parent a557015 commit 912c067

File tree

2 files changed

+21
-10
lines changed

2 files changed

+21
-10
lines changed

Diff for: spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
4343
import org.springframework.core.io.buffer.DataBuffer;
4444
import org.springframework.core.io.buffer.DataBufferLimitException;
4545
import org.springframework.core.io.buffer.DataBufferUtils;
46+
import org.springframework.core.io.buffer.PooledDataBuffer;
4647
import org.springframework.core.log.LogFormatUtils;
4748
import org.springframework.http.codec.HttpMessageDecoder;
4849
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -157,7 +158,8 @@ public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementTy
157158
catch (IOException ex) {
158159
sink.error(processException(ex));
159160
}
160-
});
161+
})
162+
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
161163
});
162164
}
163165

Diff for: spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java

+17-8
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ void canDecodeWithObjectMapperRegistrationForType() {
116116
}
117117

118118
@Test // SPR-15866
119-
public void canDecodeWithProvidedMimeType() {
119+
void canDecodeWithProvidedMimeType() {
120120
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
121121
Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(new ObjectMapper(), textJavascript);
122122

@@ -222,15 +222,15 @@ void invalidData() {
222222
}
223223

224224
@Test // gh-22042
225-
public void decodeWithNullLiteral() {
225+
void decodeWithNullLiteral() {
226226
Flux<Object> result = this.decoder.decode(Flux.concat(stringBuffer("null")),
227227
ResolvableType.forType(Pojo.class), MediaType.APPLICATION_JSON, Collections.emptyMap());
228228

229229
StepVerifier.create(result).expectComplete().verify();
230230
}
231231

232232
@Test // gh-27511
233-
public void noDefaultConstructor() {
233+
void noDefaultConstructor() {
234234
Flux<DataBuffer> input = Flux.from(stringBuffer("{\"property1\":\"foo\",\"property2\":\"bar\"}"));
235235

236236
testDecode(input, BeanWithNoDefaultConstructor.class, step -> step
@@ -251,7 +251,7 @@ void codecException() {
251251
}
252252

253253
@Test // SPR-15975
254-
public void customDeserializer() {
254+
void customDeserializer() {
255255
Mono<DataBuffer> input = stringBuffer("{\"test\": 1}");
256256

257257
testDecode(input, TestObject.class, step -> step
@@ -272,7 +272,7 @@ void bigDecimalFlux() {
272272

273273
@Test
274274
@SuppressWarnings("unchecked")
275-
public void decodeNonUtf8Encoding() {
275+
void decodeNonUtf8Encoding() {
276276
Mono<DataBuffer> input = stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.UTF_16);
277277
ResolvableType type = ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {});
278278

@@ -285,7 +285,7 @@ public void decodeNonUtf8Encoding() {
285285

286286
@Test
287287
@SuppressWarnings("unchecked")
288-
public void decodeNonUnicode() {
288+
void decodeNonUnicode() {
289289
Flux<DataBuffer> input = Flux.concat(stringBuffer("{\"føø\":\"bår\"}", StandardCharsets.ISO_8859_1));
290290
ResolvableType type = ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {});
291291

@@ -298,7 +298,7 @@ public void decodeNonUnicode() {
298298

299299
@Test
300300
@SuppressWarnings("unchecked")
301-
public void decodeMonoNonUtf8Encoding() {
301+
void decodeMonoNonUtf8Encoding() {
302302
Mono<DataBuffer> input = stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.UTF_16);
303303
ResolvableType type = ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {});
304304

@@ -311,7 +311,7 @@ public void decodeMonoNonUtf8Encoding() {
311311

312312
@Test
313313
@SuppressWarnings("unchecked")
314-
public void decodeAscii() {
314+
void decodeAscii() {
315315
Flux<DataBuffer> input = Flux.concat(stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.US_ASCII));
316316
ResolvableType type = ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {});
317317

@@ -322,6 +322,15 @@ public void decodeAscii() {
322322
null);
323323
}
324324

325+
@Test
326+
void cancelWhileDecoding() {
327+
Flux<DataBuffer> input = Flux.just(
328+
stringBuffer("[{\"bar\":\"b1\",\"foo\":\"f1\"},").block(),
329+
stringBuffer("{\"bar\":\"b2\",\"foo\":\"f2\"}]").block());
330+
331+
testDecodeCancel(input, ResolvableType.forClass(Pojo.class), null, null);
332+
}
333+
325334

326335
private Mono<DataBuffer> stringBuffer(String value) {
327336
return stringBuffer(value, StandardCharsets.UTF_8);

0 commit comments

Comments
 (0)