001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.regionserver; 019 020import java.nio.ByteBuffer; 021import java.util.concurrent.atomic.AtomicInteger; 022import org.apache.hadoop.hbase.regionserver.ChunkCreator.ChunkType; 023import org.apache.hadoop.hbase.util.Bytes; 024import org.apache.yetus.audience.InterfaceAudience; 025 026import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 027 028/** 029 * A chunk of memory out of which allocations are sliced. 030 */ 031@InterfaceAudience.Private 032public abstract class Chunk { 033 /** Actual underlying data */ 034 protected ByteBuffer data; 035 036 protected static final int UNINITIALIZED = -1; 037 protected static final int OOM = -2; 038 /** 039 * Offset for the next allocation, or the sentinel value -1 which implies that the chunk is still 040 * uninitialized. 041 */ 042 protected AtomicInteger nextFreeOffset = new AtomicInteger(UNINITIALIZED); 043 044 /** Total number of allocations satisfied from this buffer */ 045 protected AtomicInteger allocCount = new AtomicInteger(); 046 047 /** Size of chunk in bytes */ 048 protected final int size; 049 050 // The unique id associated with the chunk. 051 private final int id; 052 053 private final ChunkType chunkType; 054 055 // indicates if the chunk is formed by ChunkCreator#MemstorePool 056 private final boolean fromPool; 057 058 /** 059 * Create an uninitialized chunk. Note that memory is not allocated yet, so this is cheap. 060 * @param size in bytes 061 * @param id the chunk id 062 */ 063 public Chunk(int size, int id, ChunkType chunkType) { 064 this(size, id, chunkType, false); 065 } 066 067 /** 068 * Create an uninitialized chunk. Note that memory is not allocated yet, so this is cheap. 069 * @param size in bytes 070 * @param id the chunk id 071 * @param fromPool if the chunk is formed by pool 072 */ 073 public Chunk(int size, int id, ChunkType chunkType, boolean fromPool) { 074 this.size = size; 075 this.id = id; 076 this.chunkType = chunkType; 077 this.fromPool = fromPool; 078 } 079 080 int getId() { 081 return this.id; 082 } 083 084 ChunkType getChunkType() { 085 return this.chunkType; 086 } 087 088 boolean isFromPool() { 089 return this.fromPool; 090 } 091 092 boolean isJumbo() { 093 return chunkType == ChunkCreator.ChunkType.JUMBO_CHUNK; 094 } 095 096 boolean isIndexChunk() { 097 return chunkType == ChunkCreator.ChunkType.INDEX_CHUNK; 098 } 099 100 boolean isDataChunk() { 101 return chunkType == ChunkCreator.ChunkType.DATA_CHUNK; 102 } 103 104 /** 105 * Actually claim the memory for this chunk. This should only be called from the thread that 106 * constructed the chunk. It is thread-safe against other threads calling alloc(), who will block 107 * until the allocation is complete. 108 */ 109 public void init() { 110 assert nextFreeOffset.get() == UNINITIALIZED; 111 try { 112 allocateDataBuffer(); 113 } catch (OutOfMemoryError e) { 114 boolean failInit = nextFreeOffset.compareAndSet(UNINITIALIZED, OOM); 115 assert failInit; // should be true. 116 throw e; 117 } 118 // Mark that it's ready for use 119 // Move 4 bytes since the first 4 bytes are having the chunkid in it 120 boolean initted = nextFreeOffset.compareAndSet(UNINITIALIZED, Bytes.SIZEOF_INT); 121 // We should always succeed the above CAS since only one thread 122 // calls init()! 123 Preconditions.checkState(initted, "Multiple threads tried to init same chunk"); 124 } 125 126 abstract void allocateDataBuffer(); 127 128 /** 129 * Reset the offset to UNINITIALIZED before before reusing an old chunk 130 */ 131 void reset() { 132 if (nextFreeOffset.get() != UNINITIALIZED) { 133 nextFreeOffset.set(UNINITIALIZED); 134 allocCount.set(0); 135 } 136 } 137 138 /** 139 * Try to allocate <code>size</code> bytes from the chunk. If a chunk is tried to get allocated 140 * before init() call, the thread doing the allocation will be in busy-wait state as it will keep 141 * looping till the nextFreeOffset is set. 142 * @return the offset of the successful allocation, or -1 to indicate not-enough-space 143 */ 144 public int alloc(int size) { 145 while (true) { 146 int oldOffset = nextFreeOffset.get(); 147 if (oldOffset == UNINITIALIZED) { 148 // The chunk doesn't have its data allocated yet. 149 // Since we found this in curChunk, we know that whoever 150 // CAS-ed it there is allocating it right now. So spin-loop 151 // shouldn't spin long! 152 Thread.yield(); 153 continue; 154 } 155 if (oldOffset == OOM) { 156 // doh we ran out of ram. return -1 to chuck this away. 157 return -1; 158 } 159 160 if (oldOffset + size > data.capacity()) { 161 return -1; // alloc doesn't fit 162 } 163 // TODO : If seqID is to be written add 8 bytes here for nextFreeOFfset 164 // Try to atomically claim this chunk 165 if (nextFreeOffset.compareAndSet(oldOffset, oldOffset + size)) { 166 // we got the alloc 167 allocCount.incrementAndGet(); 168 return oldOffset; 169 } 170 // we raced and lost alloc, try again 171 } 172 } 173 174 /** Returns This chunk's backing data. */ 175 ByteBuffer getData() { 176 return this.data; 177 } 178 179 @Override 180 public String toString() { 181 return "Chunk@" + System.identityHashCode(this) + " allocs=" + allocCount.get() + "waste=" 182 + (data.capacity() - nextFreeOffset.get()); 183 } 184 185 int getNextFreeOffset() { 186 return this.nextFreeOffset.get(); 187 } 188}