mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-21 13:31:46 +00:00
Refactor maxInMemory limit handling for async XML parsing
The limit was previously enforced in XmlEventDecoder, because it is what parses incoming buffers. However, the actual caching is in Jaxb2Decoder, which holds on to XML events, but has no good way to estimate their size. After this commit XmlEventDecoder no longer enforces memory limits for async parsing. It releases each buffer immediately anyway. Instead XmlEventDecoder is only responsible to update the number of bytes received via a new ReceivedByteTracker type while Jaxb2XmlDecoder uses the same to perform limit and reset the count depending on when it is aggregating XML events. Closes gh-37031
This commit is contained in:
committed by
Brian Clozel
parent
30e3a5719e
commit
e12f0761f3
+40
-6
@@ -35,7 +35,9 @@ import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.DecodingException;
|
||||
import org.springframework.core.codec.Hints;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferLimitException;
|
||||
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MimeType;
|
||||
@@ -92,7 +94,7 @@ class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
||||
@Test
|
||||
void splitOneBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder.decode(toDataBufferMono(POJO_ROOT), null, null, HINTS);
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")));
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")), null);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(events -> {
|
||||
@@ -113,8 +115,7 @@ class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
||||
@Test
|
||||
void splitMultipleBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder.decode(toDataBufferMono(POJO_CHILD), null, null, HINTS);
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")));
|
||||
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")), null);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(events -> {
|
||||
@@ -143,6 +144,34 @@ class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitMultipleBranchesLimitExceeded() {
|
||||
|
||||
Flux<String> source = Flux.just(
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
||||
"<root><pojo><foo>foo</foo></pojo>",
|
||||
"<pojo><foo>foofoo</foo>",
|
||||
"<bar>barbar</bar></pojo>",
|
||||
"<root/>");
|
||||
|
||||
XmlEventDecoder.ReceivedByteTracker byteTracker = new XmlEventDecoder.ReceivedByteTracker(30);
|
||||
Map<String, Object> hints = Hints.from(XmlEventDecoder.BYTE_TRACKER_HINT, byteTracker);
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder.decode(source.map(this::toToDataBuffer), null, null, hints);
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")), byteTracker);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(events -> {
|
||||
assertThat(events).hasSize(5);
|
||||
assertStartElement(events.get(0), "pojo");
|
||||
assertStartElement(events.get(1), "foo");
|
||||
assertCharacters(events.get(2), "foo");
|
||||
assertEndElement(events.get(3), "foo");
|
||||
assertEndElement(events.get(4), "pojo");
|
||||
})
|
||||
.expectError(DataBufferLimitException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
private static void assertStartElement(XMLEvent event, String expectedLocalName) {
|
||||
assertThat(event.isStartElement()).isTrue();
|
||||
assertThat(event.asStartElement().getName().getLocalPart()).isEqualTo(expectedLocalName);
|
||||
@@ -263,13 +292,18 @@ class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
||||
|
||||
private Mono<DataBuffer> toDataBufferMono(String value) {
|
||||
return Mono.defer(() -> {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = this.bufferFactory.allocateBuffer(bytes.length);
|
||||
buffer.write(bytes);
|
||||
DataBuffer buffer = toToDataBuffer(value);
|
||||
return Mono.just(buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private DataBuffer toToDataBuffer(String value) {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = this.bufferFactory.allocateBuffer(bytes.length);
|
||||
buffer.write(bytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@jakarta.xml.bind.annotation.XmlType
|
||||
@XmlSeeAlso(Child.class)
|
||||
public abstract static class Parent {
|
||||
|
||||
@@ -27,7 +27,6 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferLimitException;
|
||||
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -88,28 +87,6 @@ class XmlEventDecoderTests extends AbstractLeakCheckingTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toXMLEventsWithLimit() {
|
||||
|
||||
this.decoder.setMaxInMemorySize(6);
|
||||
|
||||
Flux<String> source = Flux.just(
|
||||
"<pojo>", "<foo>", "foofoo", "</foo>", "<bar>", "barbarbar", "</bar>", "</pojo>");
|
||||
|
||||
Flux<XMLEvent> events = this.decoder.decode(
|
||||
source.map(this::stringBuffer), null, null, Collections.emptyMap());
|
||||
|
||||
StepVerifier.create(events)
|
||||
.consumeNextWith(e -> assertThat(e.isStartDocument()).isTrue())
|
||||
.consumeNextWith(e -> assertStartElement(e, "pojo"))
|
||||
.consumeNextWith(e -> assertStartElement(e, "foo"))
|
||||
.consumeNextWith(e -> assertCharacters(e, "foofoo"))
|
||||
.consumeNextWith(e -> assertEndElement(e, "foo"))
|
||||
.consumeNextWith(e -> assertStartElement(e, "bar"))
|
||||
.expectError(DataBufferLimitException.class)
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeErrorAalto() {
|
||||
Flux<DataBuffer> source = Flux.concat(
|
||||
|
||||
Reference in New Issue
Block a user