Issue #1861 - Limit total bytes pooled by ByteBufferPools.
Code cleanups + javadocs. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
259e31856a
commit
f2d15f12d5
|
@ -20,49 +20,74 @@ package org.eclipse.jetty.io;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.annotation.ManagedOperation;
|
||||
|
||||
/**
|
||||
* <p>A ByteBuffer pool where ByteBuffers are held in queues that are held in array elements.</p>
|
||||
* <p>Given a capacity {@code factor} of 1024, the first array element holds a queue of ByteBuffers
|
||||
* each of capacity 1024, the second array element holds a queue of ByteBuffers each of capacity
|
||||
* 2048, and so on.</p>
|
||||
*/
|
||||
@ManagedObject
|
||||
public class ArrayByteBufferPool implements ByteBufferPool
|
||||
{
|
||||
private final int _min;
|
||||
private final int _maxQueue;
|
||||
private final int _minCapacity;
|
||||
private final ByteBufferPool.Bucket[] _direct;
|
||||
private final ByteBufferPool.Bucket[] _indirect;
|
||||
private final int _inc;
|
||||
private final int _factor;
|
||||
|
||||
/**
|
||||
* Creates a new ArrayByteBufferPool with a default configuration.
|
||||
*/
|
||||
public ArrayByteBufferPool()
|
||||
{
|
||||
this(-1, -1, -1, -1);
|
||||
}
|
||||
|
||||
public ArrayByteBufferPool(int minSize, int increment, int maxSize)
|
||||
/**
|
||||
* Creates a new ArrayByteBufferPool with the given configuration.
|
||||
*
|
||||
* @param minCapacity the minimum ByteBuffer capacity
|
||||
* @param factor the capacity factor
|
||||
* @param maxCapacity the maximum ByteBuffer capacity
|
||||
*/
|
||||
public ArrayByteBufferPool(int minCapacity, int factor, int maxCapacity)
|
||||
{
|
||||
this(minSize,increment,maxSize,-1);
|
||||
this(minCapacity, factor, maxCapacity, -1);
|
||||
}
|
||||
|
||||
public ArrayByteBufferPool(int minSize, int increment, int maxSize, int maxQueue)
|
||||
/**
|
||||
* Creates a new ArrayByteBufferPool with the given configuration.
|
||||
*
|
||||
* @param minCapacity the minimum ByteBuffer capacity
|
||||
* @param factor the capacity factor
|
||||
* @param maxCapacity the maximum ByteBuffer capacity
|
||||
* @param maxQueueLength the maximum ByteBuffer queue length
|
||||
*/
|
||||
public ArrayByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxQueueLength)
|
||||
{
|
||||
if (minSize<=0)
|
||||
minSize=0;
|
||||
if (increment<=0)
|
||||
increment=1024;
|
||||
if (maxSize<=0)
|
||||
maxSize=64*1024;
|
||||
if (minSize>=increment)
|
||||
throw new IllegalArgumentException("minSize >= increment");
|
||||
if ((maxSize%increment)!=0 || increment>=maxSize)
|
||||
throw new IllegalArgumentException("increment must be a divisor of maxSize");
|
||||
_min=minSize;
|
||||
_inc=increment;
|
||||
if (minCapacity <= 0)
|
||||
minCapacity = 0;
|
||||
if (factor <= 0)
|
||||
factor = 1024;
|
||||
if (maxCapacity <= 0)
|
||||
maxCapacity = 64 * 1024;
|
||||
if ((maxCapacity % factor) != 0 || factor >= maxCapacity)
|
||||
throw new IllegalArgumentException("The capacity factor must be a divisor of maxCapacity");
|
||||
_minCapacity = minCapacity;
|
||||
_factor = factor;
|
||||
|
||||
_direct=new ByteBufferPool.Bucket[maxSize/increment];
|
||||
_indirect=new ByteBufferPool.Bucket[maxSize/increment];
|
||||
_maxQueue=maxQueue;
|
||||
int length = maxCapacity / factor;
|
||||
_direct = new ByteBufferPool.Bucket[length];
|
||||
_indirect = new ByteBufferPool.Bucket[length];
|
||||
|
||||
int size=0;
|
||||
for (int i=0;i<_direct.length;i++)
|
||||
int capacity = 0;
|
||||
for (int i = 0; i < _direct.length; ++i)
|
||||
{
|
||||
size+=_inc;
|
||||
_direct[i]=new ByteBufferPool.Bucket(this,size,_maxQueue);
|
||||
_indirect[i]=new ByteBufferPool.Bucket(this,size,_maxQueue);
|
||||
capacity += _factor;
|
||||
_direct[i] = new ByteBufferPool.Bucket(this, capacity, maxQueueLength);
|
||||
_indirect[i] = new ByteBufferPool.Bucket(this, capacity, maxQueueLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,9 +97,7 @@ public class ArrayByteBufferPool implements ByteBufferPool
|
|||
ByteBufferPool.Bucket bucket = bucketFor(size, direct);
|
||||
if (bucket == null)
|
||||
return newByteBuffer(size, direct);
|
||||
|
||||
return bucket.acquire(direct);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,25 +111,24 @@ public class ArrayByteBufferPool implements ByteBufferPool
|
|||
}
|
||||
}
|
||||
|
||||
@ManagedOperation(value = "Clears this ByteBufferPool", impact = "ACTION")
|
||||
public void clear()
|
||||
{
|
||||
for (int i=0;i<_direct.length;i++)
|
||||
for (int i = 0; i < _direct.length; ++i)
|
||||
{
|
||||
_direct[i].clear();
|
||||
_indirect[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBufferPool.Bucket bucketFor(int size,boolean direct)
|
||||
private ByteBufferPool.Bucket bucketFor(int capacity, boolean direct)
|
||||
{
|
||||
if (size<=_min)
|
||||
if (capacity <= _minCapacity)
|
||||
return null;
|
||||
int b=(size-1)/_inc;
|
||||
int b = (capacity - 1) / _factor;
|
||||
if (b >= _direct.length)
|
||||
return null;
|
||||
ByteBufferPool.Bucket bucket = direct?_direct[b]:_indirect[b];
|
||||
|
||||
return bucket;
|
||||
return bucketsFor(direct)[b];
|
||||
}
|
||||
|
||||
// Package local for testing
|
||||
|
|
|
@ -56,6 +56,13 @@ public interface ByteBufferPool
|
|||
*/
|
||||
public void release(ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* <p>Creates a new ByteBuffer of the given capacity and the given directness.</p>
|
||||
*
|
||||
* @param capacity the ByteBuffer capacity
|
||||
* @param direct the ByteBuffer directness
|
||||
* @return a newly allocated ByteBuffer
|
||||
*/
|
||||
default ByteBuffer newByteBuffer(int capacity, boolean direct)
|
||||
{
|
||||
return direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
|
||||
|
@ -131,10 +138,10 @@ public interface ByteBufferPool
|
|||
private final int _capacity;
|
||||
private final AtomicInteger _space;
|
||||
|
||||
public Bucket(ByteBufferPool pool, int bufferSize, int maxSize)
|
||||
public Bucket(ByteBufferPool pool, int capacity, int maxSize)
|
||||
{
|
||||
_pool = pool;
|
||||
_capacity = bufferSize;
|
||||
_capacity = capacity;
|
||||
_space = maxSize > 0 ? new AtomicInteger(maxSize) : null;
|
||||
}
|
||||
|
||||
|
@ -204,7 +211,7 @@ public interface ByteBufferPool
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Bucket@%x{%d/%d}", hashCode(), size(), _capacity);
|
||||
return String.format("%s@%x{%d/%d}", getClass().getSimpleName(), hashCode(), size(), _capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,36 +22,58 @@ import java.nio.ByteBuffer;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.annotation.ManagedOperation;
|
||||
|
||||
/**
|
||||
* <p>A ByteBuffer pool where ByteBuffers are held in queues that are held in a Map.</p>
|
||||
* <p>Given a capacity {@code factor} of 1024, the Map entry with key {@code 1} holds a
|
||||
* queue of ByteBuffers each of capacity 1024, the Map entry with key {@code 2} holds a
|
||||
* queue of ByteBuffers each of capacity 2048, and so on.</p>
|
||||
*/
|
||||
@ManagedObject
|
||||
public class MappedByteBufferPool implements ByteBufferPool
|
||||
{
|
||||
private final ConcurrentMap<Integer, Bucket> directBuffers = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Integer, Bucket> heapBuffers = new ConcurrentHashMap<>();
|
||||
private final int _factor;
|
||||
private final Function<Integer, Bucket> _newBucket;
|
||||
private final int _maxQueueLength;
|
||||
|
||||
/**
|
||||
* Creates a new MappedByteBufferPool with a default configuration.
|
||||
*/
|
||||
public MappedByteBufferPool()
|
||||
{
|
||||
this(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MappedByteBufferPool with the given capacity factor.
|
||||
*
|
||||
* @param factor the capacity factor
|
||||
*/
|
||||
public MappedByteBufferPool(int factor)
|
||||
{
|
||||
this(factor,-1,null);
|
||||
this(factor, -1);
|
||||
}
|
||||
|
||||
public MappedByteBufferPool(int factor,int maxQueue)
|
||||
{
|
||||
this(factor,maxQueue,null);
|
||||
}
|
||||
|
||||
public MappedByteBufferPool(int factor,int maxQueue,Function<Integer, Bucket> newBucket)
|
||||
/**
|
||||
* Creates a new MappedByteBufferPool with the given capacity factor and max queue length.
|
||||
*
|
||||
* @param factor the capacity factor
|
||||
* @param maxQueueLength the maximum ByteBuffer queue length
|
||||
*/
|
||||
public MappedByteBufferPool(int factor, int maxQueueLength)
|
||||
{
|
||||
_factor = factor <= 0 ? 1024 : factor;
|
||||
_newBucket = newBucket!=null?newBucket:i->new Bucket(this,i*_factor,maxQueue);
|
||||
_maxQueueLength = maxQueueLength;
|
||||
}
|
||||
|
||||
private Bucket newBucket(int key)
|
||||
{
|
||||
return new Bucket(this, key * _factor, _maxQueueLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,7 +81,6 @@ public class MappedByteBufferPool implements ByteBufferPool
|
|||
{
|
||||
int b = bucketFor(size);
|
||||
ConcurrentMap<Integer, Bucket> buffers = bucketsFor(direct);
|
||||
|
||||
Bucket bucket = buffers.get(b);
|
||||
if (bucket == null)
|
||||
return newByteBuffer(b * _factor, direct);
|
||||
|
@ -78,10 +99,11 @@ public class MappedByteBufferPool implements ByteBufferPool
|
|||
int b = bucketFor(buffer.capacity());
|
||||
ConcurrentMap<Integer, Bucket> buckets = bucketsFor(buffer.isDirect());
|
||||
|
||||
Bucket bucket = buckets.computeIfAbsent(b,_newBucket);
|
||||
Bucket bucket = buckets.computeIfAbsent(b, this::newBucket);
|
||||
bucket.release(buffer);
|
||||
}
|
||||
|
||||
@ManagedOperation(value = "Clears this ByteBufferPool", impact = "ACTION")
|
||||
public void clear()
|
||||
{
|
||||
directBuffers.values().forEach(Bucket::clear);
|
||||
|
|
|
@ -18,22 +18,23 @@
|
|||
|
||||
package org.eclipse.jetty.io;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool.Bucket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ArrayByteBufferPoolTest
|
||||
{
|
||||
@Test
|
||||
public void testMinimumRelease() throws Exception
|
||||
public void testMinimumRelease()
|
||||
{
|
||||
ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10, 100, 1000);
|
||||
ByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -55,7 +56,7 @@ public class ArrayByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testMaxRelease() throws Exception
|
||||
public void testMaxRelease()
|
||||
{
|
||||
ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10, 100, 1000);
|
||||
ByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -82,7 +83,7 @@ public class ArrayByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAcquireRelease() throws Exception
|
||||
public void testAcquireRelease()
|
||||
{
|
||||
ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10, 100, 1000);
|
||||
ByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -114,8 +115,7 @@ public class ArrayByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
public void testAcquireReleaseAcquire() throws Exception
|
||||
public void testAcquireReleaseAcquire()
|
||||
{
|
||||
ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(10, 100, 1000);
|
||||
ByteBufferPool.Bucket[] buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -142,14 +142,13 @@ public class ArrayByteBufferPoolTest
|
|||
}
|
||||
assertEquals(1, pooled);
|
||||
|
||||
assertTrue(buffer1==buffer2);
|
||||
assertTrue(buffer1!=buffer3);
|
||||
assertSame(buffer1, buffer2);
|
||||
assertNotSame(buffer1, buffer3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMaxQueue() throws Exception
|
||||
public void testMaxQueue()
|
||||
{
|
||||
ArrayByteBufferPool bufferPool = new ArrayByteBufferPool(-1, -1, -1, 2);
|
||||
|
||||
|
@ -161,7 +160,7 @@ public class ArrayByteBufferPoolTest
|
|||
Arrays.asList(buckets).forEach(b -> assertEquals(0, b.size()));
|
||||
|
||||
bufferPool.release(buffer1);
|
||||
Bucket bucket=Arrays.asList(buckets).stream().filter(b->b.size()>0).findFirst().get();
|
||||
Bucket bucket = Arrays.stream(buckets).filter(b -> b.size() > 0).findFirst().get();
|
||||
assertEquals(1, bucket.size());
|
||||
|
||||
bufferPool.release(buffer2);
|
||||
|
@ -170,5 +169,4 @@ public class ArrayByteBufferPoolTest
|
|||
bufferPool.release(buffer3);
|
||||
assertEquals(2, bucket.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,18 +26,18 @@ import org.eclipse.jetty.util.BufferUtil;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class MappedByteBufferPoolTest
|
||||
{
|
||||
@Test
|
||||
public void testAcquireRelease() throws Exception
|
||||
public void testAcquireRelease()
|
||||
{
|
||||
MappedByteBufferPool bufferPool = new MappedByteBufferPool();
|
||||
ConcurrentMap<Integer, Bucket> buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -56,7 +56,7 @@ public class MappedByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAcquireReleaseAcquire() throws Exception
|
||||
public void testAcquireReleaseAcquire()
|
||||
{
|
||||
MappedByteBufferPool bufferPool = new MappedByteBufferPool();
|
||||
ConcurrentMap<Integer, Bucket> buckets = bufferPool.bucketsFor(false);
|
||||
|
@ -76,7 +76,7 @@ public class MappedByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAcquireReleaseClear() throws Exception
|
||||
public void testAcquireReleaseClear()
|
||||
{
|
||||
MappedByteBufferPool bufferPool = new MappedByteBufferPool();
|
||||
ConcurrentMap<Integer, Bucket> buckets = bufferPool.bucketsFor(true);
|
||||
|
@ -96,11 +96,9 @@ public class MappedByteBufferPoolTest
|
|||
* In a scenario where MappedByteBufferPool is being used improperly,
|
||||
* such as releasing a buffer that wasn't created/acquired by the
|
||||
* MappedByteBufferPool, an assertion is tested for.
|
||||
*
|
||||
* @throws Exception test failure
|
||||
*/
|
||||
@Test
|
||||
public void testReleaseAssertion() throws Exception
|
||||
public void testReleaseAssertion()
|
||||
{
|
||||
int factor = 1024;
|
||||
MappedByteBufferPool bufferPool = new MappedByteBufferPool(factor);
|
||||
|
@ -137,7 +135,7 @@ public class MappedByteBufferPoolTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testMaxQueue() throws Exception
|
||||
public void testMaxQueue()
|
||||
{
|
||||
MappedByteBufferPool bufferPool = new MappedByteBufferPool(-1, 2);
|
||||
ConcurrentMap<Integer, Bucket> buckets = bufferPool.bucketsFor(false);
|
||||
|
|
Loading…
Reference in New Issue