Enforce disk usage limit when spilling part to disk

Previously, PartGenerator only enforced maxDiskUsagePerPart for body
buffers that arrived after a part had switched to file storage. The
content accumulated in memory that triggered the switch was written
to disk without any check against maxDiskUsagePerPart. As a result,
a part whose last body buffer caused the in-memory overflow was
accepted in full, even when its total size exceeded the configured
disk usage limit.

This commit checks the accumulated byte count against
maxDiskUsagePerPart before switching to file storage, and emits a
DataBufferLimitException, consistent with the existing check for
subsequent buffers.

Closes gh-35099

Signed-off-by: seonghun lee <harrisleesh@gmail.com>
This commit is contained in:
seonghun lee
2026-09-17 16:48:59 +02:00
committed by Brian Clozel
parent bb7ea37f1b
commit 94afeedad6
2 changed files with 26 additions and 0 deletions
@@ -56,6 +56,7 @@ import org.springframework.util.FastByteArrayOutputStream;
* of {@link Part} objects.
*
* @author Arjen Poutsma
* @author Seonghun Lee
* @since 5.3
*/
@SuppressWarnings("NullAway") // Dataflow analysis limitation
@@ -380,6 +381,16 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
}
private void switchToFile(DataBuffer current, long byteCount) {
if (PartGenerator.this.maxDiskUsagePerPart != -1 && byteCount > PartGenerator.this.maxDiskUsagePerPart) {
DataBufferUtils.release(current);
this.content.forEach(DataBufferUtils::release);
this.content.clear();
emitError(new DataBufferLimitException(
"Part exceeded the disk usage limit of " + PartGenerator.this.maxDiskUsagePerPart +
" bytes"));
return;
}
List<DataBuffer> content = new ArrayList<>(this.content);
content.add(current);
this.releaseOnDispose = false;
@@ -63,6 +63,7 @@ import static org.springframework.core.io.buffer.DataBufferUtils.release;
*
* @author Arjen Poutsma
* @author Brian Clozel
* @author Seonghun Lee
*/
class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
@@ -314,6 +315,20 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests {
.verify();
}
@Test // gh-35099
void exceedDiskUsageOnSpillOver() {
MockServerHttpRequest request = createRequest("files.multipart", "\"----WebKitFormBoundaryG8fJ50opQOML0oGD\"");
DefaultPartHttpMessageReader reader = new DefaultPartHttpMessageReader();
reader.setMaxInMemorySize(90);
reader.setMaxDiskUsagePerPart(99);
Flux<Part> result = reader.read(forClass(Part.class), request, emptyMap());
StepVerifier.create(result)
.expectError(DataBufferLimitException.class)
.verify();
}
@ParameterizedDefaultPartHttpMessageReaderTest
void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException {
MockServerHttpRequest request = createRequest(