/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.hadoop.hbase.regionserver; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.util.Bytes; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; /** * A chunk of memory out of which allocations are sliced. */ @InterfaceAudience.Private public abstract class Chunk { /** Actual underlying data */ protected ByteBuffer data; protected static final int UNINITIALIZED = -1; protected static final int OOM = -2; /** * Offset for the next allocation, or the sentinel value -1 which implies that the chunk is still * uninitialized. */ protected AtomicInteger nextFreeOffset = new AtomicInteger(UNINITIALIZED); /** Total number of allocations satisfied from this buffer */ protected AtomicInteger allocCount = new AtomicInteger(); /** Size of chunk in bytes */ protected final int size; // The unique id associated with the chunk. private final int id; // indicates if the chunk is formed by ChunkCreator#MemstorePool private final boolean fromPool; /** * Create an uninitialized chunk. Note that memory is not allocated yet, so * this is cheap. * @param size in bytes * @param id the chunk id */ public Chunk(int size, int id) { this(size, id, false); } /** * Create an uninitialized chunk. Note that memory is not allocated yet, so * this is cheap. * @param size in bytes * @param id the chunk id * @param fromPool if the chunk is formed by pool */ public Chunk(int size, int id, boolean fromPool) { this.size = size; this.id = id; this.fromPool = fromPool; } int getId() { return this.id; } boolean isFromPool() { return this.fromPool; } /** * Actually claim the memory for this chunk. This should only be called from the thread that * constructed the chunk. It is thread-safe against other threads calling alloc(), who will block * until the allocation is complete. */ public void init() { assert nextFreeOffset.get() == UNINITIALIZED; try { allocateDataBuffer(); } catch (OutOfMemoryError e) { boolean failInit = nextFreeOffset.compareAndSet(UNINITIALIZED, OOM); assert failInit; // should be true. throw e; } // Mark that it's ready for use // Move 4 bytes since the first 4 bytes are having the chunkid in it boolean initted = nextFreeOffset.compareAndSet(UNINITIALIZED, Bytes.SIZEOF_INT); // We should always succeed the above CAS since only one thread // calls init()! Preconditions.checkState(initted, "Multiple threads tried to init same chunk"); } abstract void allocateDataBuffer(); /** * Reset the offset to UNINITIALIZED before before reusing an old chunk */ void reset() { if (nextFreeOffset.get() != UNINITIALIZED) { nextFreeOffset.set(UNINITIALIZED); allocCount.set(0); } } /** * Try to allocate <code>size</code> bytes from the chunk. * If a chunk is tried to get allocated before init() call, the thread doing the allocation * will be in busy-wait state as it will keep looping till the nextFreeOffset is set. * @return the offset of the successful allocation, or -1 to indicate not-enough-space */ public int alloc(int size) { while (true) { int oldOffset = nextFreeOffset.get(); if (oldOffset == UNINITIALIZED) { // The chunk doesn't have its data allocated yet. // Since we found this in curChunk, we know that whoever // CAS-ed it there is allocating it right now. So spin-loop // shouldn't spin long! Thread.yield(); continue; } if (oldOffset == OOM) { // doh we ran out of ram. return -1 to chuck this away. return -1; } if (oldOffset + size > data.capacity()) { return -1; // alloc doesn't fit } // TODO : If seqID is to be written add 8 bytes here for nextFreeOFfset // Try to atomically claim this chunk if (nextFreeOffset.compareAndSet(oldOffset, oldOffset + size)) { // we got the alloc allocCount.incrementAndGet(); return oldOffset; } // we raced and lost alloc, try again } } /** * @return This chunk's backing data. */ ByteBuffer getData() { return this.data; } @Override public String toString() { return "Chunk@" + System.identityHashCode(this) + " allocs=" + allocCount.get() + "waste=" + (data.capacity() - nextFreeOffset.get()); } @VisibleForTesting int getNextFreeOffset() { return this.nextFreeOffset.get(); } }