Browse Source
Introduce a data buffer factory that can check for memory leaks in @After methods. Issue: SPR-17449pull/2017/head
Arjen Poutsma
6 years ago
2 changed files with 356 additions and 0 deletions
@ -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