Merge branch '5.1.x'

This commit is contained in:
Rossen Stoyanchev
2019-03-26 21:14:51 -04:00
21 changed files with 538 additions and 218 deletions

View File

@@ -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
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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