Add LeakAwareDataBufferFactory
Introduce a data buffer factory that can check for memory leaks in @After methods. Issue: SPR-17449
This commit is contained in:
parent
e31914bada
commit
0c0de851f4
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* DataBuffer implementation created by {@link LeakAwareDataBufferFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class LeakAwareDataBuffer implements PooledDataBuffer {
|
||||
|
||||
private final DataBuffer delegate;
|
||||
|
||||
private final AssertionError leakError;
|
||||
|
||||
private final LeakAwareDataBufferFactory dataBufferFactory;
|
||||
|
||||
private int refCount = 1;
|
||||
|
||||
|
||||
LeakAwareDataBuffer(DataBuffer delegate, LeakAwareDataBufferFactory dataBufferFactory) {
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
|
||||
this.delegate = delegate;
|
||||
this.dataBufferFactory = dataBufferFactory;
|
||||
this.leakError = createLeakError();
|
||||
}
|
||||
|
||||
private static AssertionError createLeakError() {
|
||||
AssertionError result = new AssertionError("Leak detected in test case");
|
||||
// remove first four irrelevant stack trace elements
|
||||
StackTraceElement[] oldTrace = result.getStackTrace();
|
||||
StackTraceElement[] newTrace = new StackTraceElement[oldTrace.length - 4];
|
||||
System.arraycopy(oldTrace, 4, newTrace, 0, oldTrace.length - 4);
|
||||
result.setStackTrace(newTrace);
|
||||
return result;
|
||||
}
|
||||
|
||||
AssertionError leakError() {
|
||||
return this.leakError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllocated() {
|
||||
return this.refCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledDataBuffer retain() {
|
||||
this.refCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
this.refCount--;
|
||||
return this.refCount == 0;
|
||||
}
|
||||
|
||||
// delegation
|
||||
|
||||
|
||||
@Override
|
||||
public LeakAwareDataBufferFactory factory() {
|
||||
return this.dataBufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(IntPredicate predicate, int fromIndex) {
|
||||
return this.delegate.indexOf(predicate, fromIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(IntPredicate predicate, int fromIndex) {
|
||||
return this.delegate.lastIndexOf(predicate, fromIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableByteCount() {
|
||||
return this.delegate.readableByteCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writableByteCount() {
|
||||
return this.delegate.writableByteCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readPosition() {
|
||||
return this.delegate.readPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer readPosition(int readPosition) {
|
||||
return this.delegate.readPosition(readPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writePosition() {
|
||||
return this.delegate.writePosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer writePosition(int writePosition) {
|
||||
return this.delegate.writePosition(writePosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return this.delegate.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer capacity(int newCapacity) {
|
||||
return this.delegate.capacity(newCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return this.delegate.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte read() {
|
||||
return this.delegate.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer read(byte[] destination) {
|
||||
return this.delegate.read(destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer read(byte[] destination, int offset, int length) {
|
||||
return this.delegate.read(destination, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(byte b) {
|
||||
return this.delegate.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(byte[] source) {
|
||||
return this.delegate.write(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(byte[] source, int offset, int length) {
|
||||
return this.delegate.write(source, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(DataBuffer... buffers) {
|
||||
return this.delegate.write(buffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(ByteBuffer... byteBuffers) {
|
||||
return this.delegate.write(byteBuffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer slice(int index, int length) {
|
||||
return this.delegate.slice(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return this.delegate.asByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer asByteBuffer(int index, int length) {
|
||||
return this.delegate.asByteBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() {
|
||||
return this.delegate.asInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream(boolean releaseOnClose) {
|
||||
return this.delegate.asInputStream(releaseOnClose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream asOutputStream() {
|
||||
return this.delegate.asOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof LeakAwareDataBuffer) {
|
||||
LeakAwareDataBuffer other = (LeakAwareDataBuffer) o;
|
||||
return this.delegate.equals(other.delegate);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("LeakAwareDataBuffer (%s)", this.delegate);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBufferFactory} interface that keep track of memory leaks.
|
||||
* Useful for unit tests that handle data buffers. Simply call {@link #checkForLeaks()} in
|
||||
* a JUnit {@link After} method, and any buffers have not been released will result in an
|
||||
* {@link AssertionError}.
|
||||
* <pre class="code">
|
||||
* public class MyUnitTest {
|
||||
*
|
||||
* private final LeakAwareDataBufferFactory bufferFactory =
|
||||
* new LeakAwareDataBufferFactory();
|
||||
*
|
||||
* @Test
|
||||
* public void doSomethingWithBufferFactory() {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* @After
|
||||
* public void checkForLeaks() {
|
||||
* bufferFactory.checkForLeaks();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
|
||||
private final DataBufferFactory delegate;
|
||||
|
||||
private final List<LeakAwareDataBuffer> created = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@code LeakAwareDataBufferFactory} by wrapping a
|
||||
* {@link DefaultDataBufferFactory}.
|
||||
*/
|
||||
public LeakAwareDataBufferFactory() {
|
||||
this(new DefaultDataBufferFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code LeakAwareDataBufferFactory} by wrapping the given delegate.
|
||||
* @param delegate the delegate buffer factory to wrap.
|
||||
*/
|
||||
public LeakAwareDataBufferFactory(DataBufferFactory delegate) {
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether all of the data buffers allocated by this factory have also been released.
|
||||
* If not, then an {@link AssertionError} is thrown. Typically used from a JUnit {@link After}
|
||||
* method.
|
||||
*/
|
||||
public void checkForLeaks() {
|
||||
this.created.stream()
|
||||
.filter(LeakAwareDataBuffer::isAllocated)
|
||||
.findFirst()
|
||||
.map(LeakAwareDataBuffer::leakError)
|
||||
.ifPresent(leakError -> {
|
||||
throw leakError;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeakAwareDataBuffer allocateBuffer() {
|
||||
LeakAwareDataBuffer dataBuffer =
|
||||
new LeakAwareDataBuffer(this.delegate.allocateBuffer(), this);
|
||||
this.created.add(dataBuffer);
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeakAwareDataBuffer allocateBuffer(int initialCapacity) {
|
||||
LeakAwareDataBuffer dataBuffer =
|
||||
new LeakAwareDataBuffer(this.delegate.allocateBuffer(initialCapacity), this);
|
||||
this.created.add(dataBuffer);
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer wrap(ByteBuffer byteBuffer) {
|
||||
return this.delegate.wrap(byteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer wrap(byte[] bytes) {
|
||||
return this.delegate.wrap(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer join(List<? extends DataBuffer> dataBuffers) {
|
||||
return new LeakAwareDataBuffer(this.delegate.join(dataBuffers), this);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue