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}