Merge branch '5.1.x'
This commit is contained in:
@@ -20,6 +20,8 @@ import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -28,7 +30,6 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.LeakAwareDataBufferFactory;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
@@ -36,19 +37,18 @@ import org.springframework.core.io.support.ResourceRegion;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.charset.StandardCharsets.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link ResourceRegionEncoder} class.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ResourceRegionEncoderTests {
|
||||
|
||||
private ResourceRegionEncoder encoder = new ResourceRegionEncoder();
|
||||
|
||||
private DataBufferFactory bufferFactory = new LeakAwareDataBufferFactory();
|
||||
private LeakAwareDataBufferFactory bufferFactory = new LeakAwareDataBufferFactory();
|
||||
|
||||
|
||||
@Test
|
||||
@@ -79,10 +79,13 @@ public class ResourceRegionEncoderTests {
|
||||
.consumeNextWith(stringConsumer("Spring"))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
// TODO: https://github.com/reactor/reactor-core/issues/1634
|
||||
// this.bufferFactory.checkForLeaks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEncodeMultipleResourceRegionsFileResource() throws Exception {
|
||||
public void shouldEncodeMultipleResourceRegionsFileResource() {
|
||||
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
|
||||
Flux<ResourceRegion> regions = Flux.just(
|
||||
new ResourceRegion(resource, 0, 6),
|
||||
@@ -118,6 +121,33 @@ public class ResourceRegionEncoderTests {
|
||||
.consumeNextWith(stringConsumer("\r\n--" + boundary + "--"))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
// TODO: https://github.com/reactor/reactor-core/issues/1634
|
||||
// this.bufferFactory.checkForLeaks();
|
||||
}
|
||||
|
||||
@Test // gh-
|
||||
public void cancelWithoutDemandForMultipleResourceRegions() {
|
||||
Resource resource = new ClassPathResource("ResourceRegionEncoderTests.txt", getClass());
|
||||
Flux<ResourceRegion> regions = Flux.just(
|
||||
new ResourceRegion(resource, 0, 6),
|
||||
new ResourceRegion(resource, 7, 9),
|
||||
new ResourceRegion(resource, 17, 4),
|
||||
new ResourceRegion(resource, 22, 17)
|
||||
);
|
||||
String boundary = MimeTypeUtils.generateMultipartBoundaryString();
|
||||
|
||||
Flux<DataBuffer> flux = this.encoder.encode(regions, this.bufferFactory,
|
||||
ResolvableType.forClass(ResourceRegion.class),
|
||||
MimeType.valueOf("text/plain"),
|
||||
Collections.singletonMap(ResourceRegionEncoder.BOUNDARY_STRING_HINT, boundary)
|
||||
);
|
||||
|
||||
ZeroDemandSubscriber subscriber = new ZeroDemandSubscriber();
|
||||
flux.subscribe(subscriber);
|
||||
subscriber.cancel();
|
||||
|
||||
this.bufferFactory.checkForLeaks();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,6 +172,9 @@ public class ResourceRegionEncoderTests {
|
||||
.consumeNextWith(stringConsumer("Spring"))
|
||||
.expectError(EncodingException.class)
|
||||
.verify();
|
||||
|
||||
// TODO: https://github.com/reactor/reactor-core/issues/1634
|
||||
// this.bufferFactory.checkForLeaks();
|
||||
}
|
||||
|
||||
protected Consumer<DataBuffer> stringConsumer(String expected) {
|
||||
@@ -154,4 +187,12 @@ public class ResourceRegionEncoderTests {
|
||||
}
|
||||
|
||||
|
||||
private static class ZeroDemandSubscriber extends BaseSubscriber<DataBuffer> {
|
||||
|
||||
@Override
|
||||
protected void hookOnSubscribe(Subscription subscription) {
|
||||
// Just subscribe without requesting
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -123,9 +123,23 @@ public abstract class AbstractDataBufferAllocatingTestCase {
|
||||
if (this.bufferFactory instanceof NettyDataBufferFactory) {
|
||||
ByteBufAllocator allocator = ((NettyDataBufferFactory) this.bufferFactory).getByteBufAllocator();
|
||||
if (allocator instanceof PooledByteBufAllocator) {
|
||||
PooledByteBufAllocatorMetric metric = ((PooledByteBufAllocator) allocator).metric();
|
||||
long total = getAllocations(metric.directArenas()) + getAllocations(metric.heapArenas());
|
||||
assertEquals("ByteBuf Leak: " + total + " unreleased allocations", 0, total);
|
||||
Instant start = Instant.now();
|
||||
while (true) {
|
||||
PooledByteBufAllocatorMetric metric = ((PooledByteBufAllocator) allocator).metric();
|
||||
long total = getAllocations(metric.directArenas()) + getAllocations(metric.heapArenas());
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
if (Instant.now().isBefore(start.plus(Duration.ofSeconds(5)))) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
assertEquals("ByteBuf Leak: " + total + " unreleased allocations", 0, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,8 +35,11 @@ import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -184,6 +187,20 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
.verify();
|
||||
}
|
||||
|
||||
// TODO: Remove ignore after https://github.com/reactor/reactor-core/issues/1634
|
||||
@Ignore
|
||||
@Test // gh-22107
|
||||
public void readAsynchronousFileChannelCancelWithoutDemand() throws Exception {
|
||||
URI uri = this.resource.getURI();
|
||||
Flux<DataBuffer> flux = DataBufferUtils.readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(Paths.get(uri), StandardOpenOption.READ),
|
||||
this.bufferFactory, 3);
|
||||
|
||||
BaseSubscriber<DataBuffer> subscriber = new ZeroDemandSubscriber();
|
||||
flux.subscribe(subscriber);
|
||||
subscriber.cancel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readResource() throws Exception {
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(this.resource, this.bufferFactory, 3);
|
||||
@@ -735,5 +752,12 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
}
|
||||
|
||||
|
||||
private static class ZeroDemandSubscriber extends BaseSubscriber<DataBuffer> {
|
||||
|
||||
@Override
|
||||
protected void hookOnSubscribe(Subscription subscription) {
|
||||
// Just subscribe without requesting
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,14 @@
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.After;
|
||||
|
||||
@@ -37,6 +42,9 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LeakAwareDataBufferFactory.class);
|
||||
|
||||
|
||||
private final DataBufferFactory delegate;
|
||||
|
||||
private final List<LeakAwareDataBuffer> created = new ArrayList<>();
|
||||
@@ -65,13 +73,27 @@ public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
* method.
|
||||
*/
|
||||
public void checkForLeaks() {
|
||||
this.created.stream()
|
||||
.filter(LeakAwareDataBuffer::isAllocated)
|
||||
.findFirst()
|
||||
.map(LeakAwareDataBuffer::leakError)
|
||||
.ifPresent(leakError -> {
|
||||
throw leakError;
|
||||
});
|
||||
Instant start = Instant.now();
|
||||
while (true) {
|
||||
if (this.created.stream().noneMatch(LeakAwareDataBuffer::isAllocated)) {
|
||||
return;
|
||||
}
|
||||
if (Instant.now().isBefore(start.plus(Duration.ofSeconds(5)))) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
List<AssertionError> errors = this.created.stream()
|
||||
.filter(LeakAwareDataBuffer::isAllocated)
|
||||
.map(LeakAwareDataBuffer::leakError)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
errors.forEach(it -> logger.error("Leaked error: ", it));
|
||||
throw new AssertionError(errors.size() + " buffer leaks detected (see logs above)");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user