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:
Simone Bordet 2019-02-25 16:14:41 +01:00
parent 259e31856a
commit f2d15f12d5
5 changed files with 200 additions and 153 deletions

View File

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

View File

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

View File

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

View File

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

View File

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