package io.undertow.testutils;
import io.undertow.connector.ByteBufferPool;
import io.undertow.connector.PooledByteBuffer;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Stuart Douglas
*/
public class DebuggingSlicePool implements ByteBufferPool{
/**
* context that can be added to allocations to give more information about buffer leaks, useful when debugging buffer leaks
*/
private static final ThreadLocal<String> ALLOCATION_CONTEXT = new ThreadLocal<>();
static final Set<DebuggingBuffer> BUFFERS = Collections.newSetFromMap(new ConcurrentHashMap<DebuggingBuffer, Boolean>());
static volatile String currentLabel;
private final ByteBufferPool delegate;
private final ByteBufferPool arrayBacked;
public DebuggingSlicePool(ByteBufferPool delegate) {
this.delegate = delegate;
if(delegate.isDirect()) {
this.arrayBacked = new DebuggingSlicePool(delegate.getArrayBackedPool());
} else {
this.arrayBacked = this;
}
}
public static void addContext(String context) {
ALLOCATION_CONTEXT.set(context);
}
@Override
public PooledByteBuffer allocate() {
final PooledByteBuffer delegate = this.delegate.allocate();
return new DebuggingBuffer(delegate, currentLabel);
}
@Override
public ByteBufferPool getArrayBackedPool() {
return arrayBacked;
}
@Override
public void close() {
delegate.close();
}
@Override
public int getBufferSize() {
return delegate.getBufferSize();
}
@Override
public boolean isDirect() {
return delegate.isDirect();
}
static class DebuggingBuffer implements PooledByteBuffer {
private static final AtomicInteger allocationCount = new AtomicInteger();
private final RuntimeException allocationPoint;
private final PooledByteBuffer delegate;
private final String label;
private final int no;
private volatile boolean free = false;
private RuntimeException freePoint;
DebuggingBuffer(PooledByteBuffer delegate, String label) {
this.delegate = delegate;
this.label = label;
this.no = allocationCount.getAndIncrement();
String ctx = ALLOCATION_CONTEXT.get();
ALLOCATION_CONTEXT.remove();
allocationPoint = new RuntimeException(delegate.getBuffer() + " NO: " + no + " " + (ctx == null ? "[NO_CONTEXT]" : ctx));
BUFFERS.add(this);
}
@Override
public void close() {
if(free) {
return;
}
freePoint = new RuntimeException("FREE POINT");
free = true;
BUFFERS.remove(this);
delegate.close();
}
@Override
public boolean isOpen() {
return !free;
}
@Override
public ByteBuffer getBuffer() throws IllegalStateException {
if(free) {
throw new IllegalStateException("Buffer already freed, free point: ", freePoint);
}
return delegate.getBuffer();
}
RuntimeException getAllocationPoint() {
return allocationPoint;
}
String getLabel() {
return label;
}
@Override
public String toString() {
return "[debug:"+no+"]" + delegate.toString() ;
}
}
}