/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 io.undertow.server.handlers.cache;

import org.xnio.BufferAllocator;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

A limited buffer pooled allocator. This pool uses a series of buffer regions to back the returned pooled buffers. When the buffer is no longer needed, it should be freed back into the pool; failure to do so will cause the corresponding buffer area to be unavailable until the buffer is garbage-collected.
Author:David M. Lloyd, Jason T. Greene
/** * A limited buffer pooled allocator. This pool uses a series of buffer regions to back the * returned pooled buffers. When the buffer is no longer needed, it should be freed back into the pool; failure * to do so will cause the corresponding buffer area to be unavailable until the buffer is garbage-collected. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> * @author Jason T. Greene */
public final class LimitedBufferSlicePool { private static final AtomicIntegerFieldUpdater regionUpdater = AtomicIntegerFieldUpdater.newUpdater(LimitedBufferSlicePool.class, "regionsUsed"); private final Queue<Slice> sliceQueue = new ConcurrentLinkedQueue<>(); private final BufferAllocator<ByteBuffer> allocator; private final int bufferSize; private final int buffersPerRegion; private final int maxRegions; private volatile int regionsUsed;
Construct a new instance.
Params:
  • allocator – the buffer allocator to use
  • bufferSize – the size of each buffer
  • maxRegionSize – the maximum region size for each backing buffer
  • maxRegions – the maximum regions to create, zero for unlimited
/** * Construct a new instance. * * @param allocator the buffer allocator to use * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer * @param maxRegions the maximum regions to create, zero for unlimited */
public LimitedBufferSlicePool(final BufferAllocator<ByteBuffer> allocator, final int bufferSize, final int maxRegionSize, final int maxRegions) { if (bufferSize <= 0) { throw new IllegalArgumentException("Buffer size must be greater than zero"); } if (maxRegionSize < bufferSize) { throw new IllegalArgumentException("Maximum region size must be greater than or equal to the buffer size"); } buffersPerRegion = maxRegionSize / bufferSize; this.bufferSize = bufferSize; this.allocator = allocator; this.maxRegions = maxRegions; }
Construct a new instance.
Params:
  • allocator – the buffer allocator to use
  • bufferSize – the size of each buffer
  • maxRegionSize – the maximum region size for each backing buffer
/** * Construct a new instance. * * @param allocator the buffer allocator to use * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer */
public LimitedBufferSlicePool(BufferAllocator<ByteBuffer> allocator, int bufferSize, int maxRegionSize) { this(allocator, bufferSize, maxRegionSize, 0); }
Construct a new instance, using a direct buffer allocator.
Params:
  • bufferSize – the size of each buffer
  • maxRegionSize – the maximum region size for each backing buffer
/** * Construct a new instance, using a direct buffer allocator. * * @param bufferSize the size of each buffer * @param maxRegionSize the maximum region size for each backing buffer */
public LimitedBufferSlicePool(final int bufferSize, final int maxRegionSize) { this(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, bufferSize, maxRegionSize); }
Allocates a new byte buffer if possible
Returns:new buffer or null if none available
/** * Allocates a new byte buffer if possible * * @return new buffer or null if none available **/
public PooledByteBuffer allocate() { final Queue<Slice> sliceQueue = this.sliceQueue; final Slice slice = sliceQueue.poll(); if (slice == null && (maxRegions <= 0 || regionUpdater.getAndIncrement(this) < maxRegions)) { final int bufferSize = this.bufferSize; final int buffersPerRegion = this.buffersPerRegion; final ByteBuffer region = allocator.allocate(buffersPerRegion * bufferSize); int idx = bufferSize; for (int i = 1; i < buffersPerRegion; i ++) { sliceQueue.add(new Slice(region, idx, bufferSize)); idx += bufferSize; } final Slice newSlice = new Slice(region, 0, bufferSize); return new PooledByteBuffer(newSlice, newSlice.slice(), sliceQueue); } if (slice == null) { return null; } return new PooledByteBuffer(slice, slice.slice(), sliceQueue); } public boolean canAllocate(int slices) { if (regionsUsed < maxRegions) return true; if (sliceQueue.isEmpty()) return false; Iterator iterator = sliceQueue.iterator(); for (int i = 0; i < slices; i++) { if (! iterator.hasNext()) { return false; } try { iterator.next(); } catch (NoSuchElementException e) { return false; } } return true; } public static final class PooledByteBuffer { private final Slice region; private final Queue<Slice> slices; volatile ByteBuffer buffer; private static final AtomicReferenceFieldUpdater<PooledByteBuffer, ByteBuffer> bufferUpdater = AtomicReferenceFieldUpdater.newUpdater(PooledByteBuffer.class, ByteBuffer.class, "buffer"); private PooledByteBuffer(final Slice region, final ByteBuffer buffer, final Queue<Slice> slices) { this.region = region; this.buffer = buffer; this.slices = slices; } public void free() { if (bufferUpdater.getAndSet(this, null) != null) { // trust the user, repool the buffer slices.add(region); } } public ByteBuffer getBuffer() { final ByteBuffer buffer = this.buffer; if (buffer == null) { throw new IllegalStateException(); } return buffer; } public String toString() { return "Pooled buffer " + buffer; } } private static final class Slice { private final ByteBuffer parent; private final int start; private final int size; private Slice(final ByteBuffer parent, final int start, final int size) { this.parent = parent; this.start = start; this.size = size; } ByteBuffer slice() { return ((ByteBuffer)parent.duplicate().position(start).limit(start+size)).slice(); } } }