Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.bigdata.io.DirectBufferPoolAllocator Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Sep 8, 2010
*/
package com.bigdata.io;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import com.bigdata.service.ResourceService;
import com.bigdata.util.concurrent.Haltable;
/**
* An allocator for {@link ByteBuffer} slices backed by direct
* {@link ByteBuffer}s allocated against a {@link DirectBufferPool}.
*
* @author Bryan Thompson
* @version $Id$
*
* @todo Make the type of the identifier for the {@link IAllocation} generic
* using a factory pattern (we need {@link UUID} for scale-out, but the
* class could be reused for other purposes as well). [The allocation
* context identifier should continue to be an application specified
* object.]
*/
public class DirectBufferPoolAllocator {
/**
* The pool from which the direct {@link ByteBuffer}s are allocated.
*/
private final DirectBufferPool directBufferPool;
/**
* The set of allocation contexts.
*/
private final ConcurrentHashMap allocationContexts = new ConcurrentHashMap();
/**
* The set of {@link IAllocation} outstanding against the
* {@link #directBufferPool}.
*/
private final ConcurrentHashMap allocations = new ConcurrentHashMap();
/**
* @todo Maybe replace this with a private {@link Haltable} (or extend
* {@link Haltable}) so we can test {@link Haltable#halted()} in
* critical methods? If we expose the {@link Haltable} then the
* {@link ResourceService} can also check it to see whether all
* allocations have been invalidated. However, that will not help us
* to invalidate a specific {@link IAllocationContext}. For that
* purpose we would need to do pretty much the same thing recursively.
*/
private final AtomicBoolean open = new AtomicBoolean(true);
/**
*
* @param pool
* The pool from which the direct {@link ByteBuffer}s are
* allocated.
*/
public DirectBufferPoolAllocator(final DirectBufferPool pool) {
this.directBufferPool = pool;
}
/**
* Extended to {@link #close()} the allocator.
*/
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Releases all {@link AllocationContext}s and all direct {@link ByteBuffer}
* s which they are using.
*/
public void close() {
if (open.compareAndSet(true/* expect */, false/* update */)) {
for (AllocationContext c : allocationContexts.values()) {
c.release();
}
}
}
/**
* The maximum #of bytes in a single {@link IAllocation}.
*/
public int getMaxSlotSize() {
return directBufferPool.getBufferCapacity();
}
/**
* Return an allocation context for the key. If none exists for that key,
* then one is atomically created and returned.
*
* @param key
* A key which uniquely identifies that context. The key will be
* inserted into a hash table and therefore must have appropriate
* hashCode() and equals() methods.
*
* @return The allocation context.
*/
public IAllocationContext getAllocationContext(final Object key) {
AllocationContext c = allocationContexts.get(key);
if (c == null) {
final AllocationContext t = allocationContexts.putIfAbsent(key,
c = new AllocationContext(key));
if (t != null) {
// lost the race to another thread.
c = t;
}
}
return c;
}
/**
* Return the allocation associated with that id.
*
* @param id
* The allocation identifier.
*
* @return The allocation -or- null
if there is no such
* allocation.
*/
public IAllocation getAllocation(final UUID id) {
return allocations.get(id);
}
// /**
// * A direct {@link ByteBuffer} allocated from the {@link #directBufferPool}
// * together with the identifier assigned to that {@link ByteBuffer} (we can
// * not directly insert {@link ByteBuffer}s into the keys of a hash map since
// * their hash code is a function of their content).
// */
// private class DirectBufferAllocation {
//
// private final Long id;
//
// private final ByteBuffer directBuffer;
//
// public DirectBufferAllocation(final Long id,
// final ByteBuffer directBuffer) {
//
// if (id == null)
// throw new IllegalArgumentException();
//
// if (directBuffer == null)
// throw new IllegalArgumentException();
//
// this.id = id;
//
// this.directBuffer = directBuffer;
//
// }
//
// }
/**
* An allocation context links some application specified key with a list
* of direct {@link ByteBuffer}s on which allocations have been made by
* the application.
*/
public interface IAllocationContext {
/**
* Allocate a series of {@link ByteBuffer} slices on which the
* application may write data. The application is encouraged to maintain
* the order of the allocations in the array in order to preserve the
* ordering of data written onto those allocation.
*
* @param nbytes
* The #of bytes required.
*
* @return The {@link UUID}s of those allocations.
*
* @throws InterruptedException
*/
IAllocation[] alloc(int nbytes) throws InterruptedException;
/**
* Release all allocations made against this allocation context.
*/
void release();
}
/**
* An allocation against a direct {@link ByteBuffer}.
*/
public interface IAllocation {
/** The allocation identifier. */
public UUID getId();
/**
* The allocated {@link ByteBuffer#slice()}.
*/
public ByteBuffer getSlice();
/**
* Release this allocation.
*
* Note: The implementation is encouraged to release the associated
* direct {@link ByteBuffer} if there are no remaining allocations
* against it and MAY made the slice of the buffer available for
* reallocation.
*
* Note: An {@link InterruptedException} MAY be thrown. This allows us
* to handle cases where a concurrent process (such as a query) was
* halted and its component threads were interrupted. By looking for the
* interrupt, we can avoid attempts to release an allocation in some
* thread where the entire {@link IAllocationContext} has already been
* released by another thread.
*
* @throws InterruptedException
*/
public void release() throws InterruptedException;
}
/**
* An allocation against a direct {@link ByteBuffer}.
*/
// Note: package private for the unit tests.
/*private*/ class Allocation implements IAllocation {
private final AllocationContext allocationContext;
/** The allocation identifier. */
final private UUID id;
/**
* The direct {@link ByteBuffer} against which the allocation was made.
*
* @todo Allow incremental recycling of allocations. To do this we would
* keep an allocation map
*
* @todo Allow incremental release of direct buffers as allocations are
* released.
*/
// Note: package private for the unit tests.
final /*private*/ IBufferAccess nativeBuffer;
/**
* A {@link ByteBuffer#slice()} onto the allocated region of the
* {@link #nativeBuffer}. The slice is cleared when the allocation is
* released.
*/
private volatile ByteBuffer allocatedSlice;
public UUID getId() {
return id;
}
public ByteBuffer getSlice() {
if (!open.get()) {
// allocator was closed.
throw new IllegalStateException();
}
final ByteBuffer t = allocatedSlice;
if(t == null) {
// slice was released.
throw new IllegalStateException();
}
return t;
}
private Allocation(final AllocationContext allocationContext,
final IBufferAccess nativeBuffer, final ByteBuffer allocatedSlice) {
if (allocationContext == null)
throw new IllegalArgumentException();
if (nativeBuffer == null)
throw new IllegalArgumentException();
if (allocatedSlice == null)
throw new IllegalArgumentException();
this.allocationContext = allocationContext;
this.id = UUID.randomUUID();
this.nativeBuffer = nativeBuffer;
this.allocatedSlice = allocatedSlice;
}
public void release() throws InterruptedException {
allocationContext.release(this);
allocatedSlice = null;
}
public String toString() {
return getClass().getName() + "{id=" + id + ",slice="
+ allocatedSlice + ",context=" + allocationContext + "}";
}
} // class Allocation
/**
* An allocation context links some application specified key with a list of
* direct {@link ByteBuffer}s on which allocations have been made by the
* application.
*/
// Note: package private for the unit tests.
/*private*/ class AllocationContext implements IAllocationContext {
/**
* The application key for this allocation context.
*/
private final Object key;
/**
* Lock guarding allocations made by this allocation context.
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* The set of native {@link ByteBuffer}s in use by this allocation
* context. Last element in the list is the {@link ByteBuffer} against
* which allocations are currently being made.
*/
private final LinkedList directBuffers = new LinkedList();
/**
* The set of native {@link ByteBuffer}s in use by this allocation
* context. The positive of in that buffer is the next byte available in
* that buffer. The limit is the buffer capacity. Mutable views of this
* buffer object are published by {@link #alloc(int)}. Those views have
* independent position, offset, and limit from the original
* {@link ByteBuffer}.
*/
private final ConcurrentHashMap allocations = new ConcurrentHashMap();
/**
* Human readable summary (non-blocking).
*
* Note: The #of native buffers is approximate since this method does
* not acquire the lock guarding that field and thus can not ensure
* timely visibility.
*/
public String toString() {
return getClass().getName() + "{key=" + key + ",#allocations="
+ allocations.size() + ",#nativeBuffers="
+ directBuffers.size() + "}";
}
public AllocationContext(final Object key) {
if (key == null)
throw new IllegalArgumentException();
this.key = key;
}
public IAllocation[] alloc(int nbytes) throws InterruptedException {
if (nbytes <= 0)
throw new IllegalArgumentException();
lock.lock();
try {
final LinkedList allocations = new LinkedList();
while (nbytes > 0) {
IBufferAccess directBuffer = directBuffers.peekLast();
if (directBuffer == null) {
if (!open.get()) {
/*
* The allocation pool is closed.
*
* Note: The lock serializes these decisions since
* otherwise allocations could be made concurrent
* with a shutdown and release of the buffers in the
* pool which would be a memory leak.
*/
throw new IllegalStateException();
}
directBuffers.add(directBuffer = directBufferPool
.acquire());
}
final ByteBuffer nativeBuffer = directBuffer.buffer();
final int remaining = nativeBuffer.remaining();
final int allocSize = Math.min(remaining, nbytes);
final int start = nativeBuffer.position();
final int limit = start + allocSize;
nativeBuffer.limit(limit);
// create the slice.
final Allocation a = new Allocation(this, directBuffer,
nativeBuffer.slice());
// restore limit to the remaining capacity.
nativeBuffer.limit(nativeBuffer.capacity());
// advance position over the allocated slice.
nativeBuffer.position(limit);
// remember allocation in the global scope.
DirectBufferPoolAllocator.this.allocations.put(a.id, a);
// remember allocation in the allocation context scope.
this.allocations.put(a.id, a);
// add to list of allocations made for this request.
allocations.add(a);
// subtract out the #of bytes allocated.
nbytes -= allocSize;
}
return allocations.toArray(new IAllocation[allocations.size()]);
} finally {
lock.unlock();
}
}
/**
* Release all direct {@link ByteBuffer}s associated with this
* allocation context back to the {@link #directBufferPool}. If the
* current thread is interrupted, the interrupt will be trapped and the
* thread will be interrupted on exit (after all allocations have been
* released).
*/
public void release() {
boolean interrupted = false;
// Note: lock is NOT interruptable.
lock.lock();
try {
for(Allocation a : allocations.values()) {
// clear the slice reference.
a.allocatedSlice = null;
}
// clear collection.
allocations.clear();
// release backing buffers.
final Iterator bitr = directBuffers.iterator();
while(bitr.hasNext()) {
final IBufferAccess b = bitr.next();
while (true) {
try {
b.release();
break;
} catch (InterruptedException e) {
interrupted = true;
} finally {
bitr.remove();
}
}
}
} finally {
lock.unlock();
}
if (interrupted)
Thread.currentThread().interrupt();
}
/**
* Release the allocation.
*
* @throws InterruptedException
* if the thread was interrupted
*/
private void release(final Allocation a) throws InterruptedException {
if (a == null)
throw new IllegalArgumentException();
lock.lockInterruptibly();
try {
if (!allocations.remove(a.id, a)) {
/*
* The allocation was not found in the map.
*/
throw new RuntimeException();
}
} finally {
lock.unlock();
}
}
}
/**
* Copy the caller's data onto the ordered array of allocations. The
* position of each {@link IAllocation#getSlice() allocation} will be
* advanced by the #of bytes copied into that allocation.
*
* @param src
* The source data.
* @param a
* The allocations.
*
* @throws BufferOverflowException
* if there is not enough room in the allocations for the data
* to be copied.
*/
static public void put(final byte[] src, final IAllocation[] a) {
int offset = 0;
int remaining = src.length;
for (int i = 0; i < a.length && remaining > 0; i++) {
final ByteBuffer slice = a[i].getSlice();
final int length = Math.min(remaining, slice.remaining());
slice.put(src, offset, length);
offset += length;
remaining -= length;
}
if (remaining > 0)
throw new BufferOverflowException();
}
/**
* Copy the caller's data onto the ordered array of allocations. The
* position of each {@link IAllocation#getSlice() allocation} will be
* advanced by the #of bytes copied into that allocation.
*
* @param src
* The source data.
* @param a
* The allocations.
*
* @throws BufferOverflowException
* if there is not enough room in the allocations for the data
* to be copied.
*/
static public void put(final ByteBuffer src, final IAllocation[] a) {
int remaining = src.remaining();
for (int i = 0; i < a.length && remaining > 0; i++) {
final ByteBuffer slice = a[i].getSlice();
final int length = Math.min(remaining, slice.remaining());
slice.put(src);
remaining -= length;
}
if (remaining > 0)
throw new BufferOverflowException();
}
}