View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io.hfile;
19  
20  import java.io.DataInputStream;
21  import java.io.DataOutput;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.nio.ByteBuffer;
26  import java.util.concurrent.atomic.AtomicReference;
27  import java.util.concurrent.locks.Lock;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.fs.FSDataInputStream;
33  import org.apache.hadoop.fs.FSDataOutputStream;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.Cell;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.classification.InterfaceAudience;
38  import org.apache.hadoop.hbase.fs.HFileSystem;
39  import org.apache.hadoop.hbase.io.ByteArrayOutputStream;
40  import org.apache.hadoop.hbase.io.ByteBufferInputStream;
41  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
42  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
43  import org.apache.hadoop.hbase.io.encoding.HFileBlockDecodingContext;
44  import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultDecodingContext;
45  import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultEncodingContext;
46  import org.apache.hadoop.hbase.io.encoding.HFileBlockEncodingContext;
47  import org.apache.hadoop.hbase.util.ByteBufferUtils;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.ChecksumType;
50  import org.apache.hadoop.hbase.util.ClassSize;
51  import org.apache.hadoop.io.IOUtils;
52  
53  import com.google.common.annotations.VisibleForTesting;
54  import com.google.common.base.Preconditions;
55  
56  /**
57   * Reads {@link HFile} version 2 blocks to HFiles and via {@link Cacheable} Interface to caches.
58   * Version 2 was introduced in hbase-0.92.0. No longer has support for version 1 blocks since
59   * hbase-1.3.0.
60   *
61   * <p>Version 1 was the original file block. Version 2 was introduced when we changed the hbase file
62   * format to support multi-level block indexes and compound bloom filters (HBASE-3857).
63   *
64   * <h3>HFileBlock: Version 2</h3>
65   * In version 2, a block is structured as follows:
66   * <ul>
67   * <li><b>Header:</b> See Writer#putHeader() for where header is written; header total size is
68   * HFILEBLOCK_HEADER_SIZE
69   * <ul>
70   * <li>0. blockType: Magic record identifying the {@link BlockType} (8 bytes):
71   * e.g. <code>DATABLK*</code>
72   * <li>1. onDiskSizeWithoutHeader: Compressed -- a.k.a 'on disk' -- block size, excluding header,
73   * but including tailing checksum bytes (4 bytes)
74   * <li>2. uncompressedSizeWithoutHeader: Uncompressed block size, excluding header, and excluding
75   * checksum bytes (4 bytes)
76   * <li>3. prevBlockOffset: The offset of the previous block of the same type (8 bytes). This is
77   * used to navigate to the previous block without having to go to the block index
78   * <li>4: For minorVersions &gt;=1, the ordinal describing checksum type (1 byte)
79   * <li>5: For minorVersions &gt;=1, the number of data bytes/checksum chunk (4 bytes)
80   * <li>6: onDiskDataSizeWithHeader: For minorVersions &gt;=1, the size of data 'on disk', including
81   * header, excluding checksums (4 bytes)
82   * </ul>
83   * </li>
84   * <li><b>Raw/Compressed/Encrypted/Encoded data:</b> The compression
85   * algorithm is the same for all the blocks in an {@link HFile}. If compression is NONE, this is
86   * just raw, serialized Cells.
87   * <li><b>Tail:</b> For minorVersions &gt;=1, a series of 4 byte checksums, one each for
88   * the number of bytes specified by bytesPerChecksum.
89   * </ul>
90   * <h3>Caching</h3>
91   * Caches cache whole blocks with trailing checksums if any. We then tag on some metadata, the
92   * content of BLOCK_METADATA_SPACE which will be flag on if we are doing 'hbase'
93   * checksums and then the offset into the file which is needed when we re-make a cache key
94   * when we return the block to the cache as 'done'. See {@link Cacheable#serialize(ByteBuffer, boolean)} and
95   * {@link Cacheable#getDeserializer()}.
96   *
97   * <p>TODO: Should we cache the checksums? Down in Writer#getBlockForCaching(CacheConfig) where
98   * we make a block to cache-on-write, there is an attempt at turning off checksums. This is not the
99   * only place we get blocks to cache. We also will cache the raw return from an hdfs read. In this
100  * case, the checksums may be present. If the cache is backed by something that doesn't do ECC,
101  * say an SSD, we might want to preserve checksums. For now this is open question.
102  * <p>TODO: Over in BucketCache, we save a block allocation by doing a custom serialization.
103  * Be sure to change it if serialization changes in here. Could we add a method here that takes an
104  * IOEngine and that then serializes to it rather than expose our internals over in BucketCache?
105  * IOEngine is in the bucket subpackage. Pull it up? Then this class knows about bucketcache. Ugh.
106  */
107 @InterfaceAudience.Private
108 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="HE_EQUALS_USE_HASHCODE",
109   justification="Fix!!! Fine for now bug FIXXXXXXX!!!!")
110 public class HFileBlock implements Cacheable {
111   private static final Log LOG = LogFactory.getLog(HFileBlock.class);
112 
113   /** Type of block. Header field 0. */
114   private BlockType blockType;
115 
116   /**
117    * Size on disk excluding header, including checksum. Header field 1.
118    * @see Writer#putHeader(byte[], int, int, int, int)
119    */
120   private int onDiskSizeWithoutHeader;
121 
122   /**
123    * Size of pure data. Does not include header or checksums. Header field 2.
124    * @see Writer#putHeader(byte[], int, int, int, int)
125    */
126   private int uncompressedSizeWithoutHeader;
127 
128   /**
129    * The offset of the previous block on disk. Header field 3.
130    * @see Writer#putHeader(byte[], int, int, int, int)
131    */
132   private long prevBlockOffset;
133 
134   /**
135    * Size on disk of header + data. Excludes checksum. Header field 6,
136    * OR calculated from {@link #onDiskSizeWithoutHeader} when using HDFS checksum.
137    * @see Writer#putHeader(byte[], int, int, int, int)
138    */
139   private int onDiskDataSizeWithHeader;
140 
141 
142   /**
143    * The in-memory representation of the hfile block. Can be on or offheap. Can be backed by
144    * a single ByteBuffer or by many. Make no assumptions.
145    *
146    * <p>Be careful reading from this <code>buf</code>. Duplicate and work on the duplicate or if
147    * not, be sure to reset position and limit else trouble down the road.
148    *
149    * <p>TODO: Make this read-only once made.
150    */
151   private ByteBuffer buf;
152 
153   /** Meta data that holds meta information on the hfileblock.
154    */
155   private HFileContext fileContext;
156 
157   /**
158    * The offset of this block in the file. Populated by the reader for
159    * convenience of access. This offset is not part of the block header.
160    */
161   private long offset = UNSET;
162 
163   /**
164    * The on-disk size of the next block, including the header and checksums if present, obtained by
165    * peeking into the first {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes of the next block's
166    * header, or UNSET if unknown.
167    *
168    * Blocks try to carry the size of the next block to read in this data member. They will even have
169    * this value when served from cache. Could save a seek in the case where we are iterating through
170    * a file and some of the blocks come from cache. If from cache, then having this info to hand
171    * will save us doing a seek to read the header so we can read the body of a block.
172    * TODO: see how effective this is at saving seeks.
173    */
174   private int nextBlockOnDiskSize = UNSET;
175 
176   /**
177    * On a checksum failure, do these many succeeding read requests using hdfs checksums before
178    * auto-reenabling hbase checksum verification.
179    */
180   static final int CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD = 3;
181 
182   private static int UNSET = -1;
183   public static final boolean FILL_HEADER = true;
184   public static final boolean DONT_FILL_HEADER = false;
185 
186   public static final int BYTE_BUFFER_HEAP_SIZE = (int) ClassSize.estimateBase(
187       ByteBuffer.wrap(new byte[0], 0, 0).getClass(), false);
188 
189   /**
190    * Space for metadata on a block that gets stored along with the block when we cache it.
191    * There are a few bytes stuck on the end of the HFileBlock that we pull in from HDFS (note,
192    * when we read from HDFS, we pull in an HFileBlock AND the header of the next block if one).
193    * 8 bytes are offset of this block (long) in the file. Offset is important because
194    * used when we remake the CacheKey when we return the block to cache when done. There is also
195    * a flag on whether checksumming is being done by hbase or not. See class comment for note on
196    * uncertain state of checksumming of blocks that come out of cache (should we or should we not?).
197    * Finally there 4 bytes to hold the length of the next block which can save a seek on occasion.
198    * <p>This EXTRA came in with original commit of the bucketcache, HBASE-7404. Was formerly
199    * known as EXTRA_SERIALIZATION_SPACE.
200    */
201   static final int BLOCK_METADATA_SPACE = Bytes.SIZEOF_BYTE + Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
202 
203   /**
204    * Each checksum value is an integer that can be stored in 4 bytes.
205    */
206   static final int CHECKSUM_SIZE = Bytes.SIZEOF_INT;
207 
208   static final byte[] DUMMY_HEADER_NO_CHECKSUM =
209       new byte[HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM];
210 
211   /**
212    * Used deserializing blocks from Cache.
213    *
214    * <code>
215    * ++++++++++++++
216    * + HFileBlock +
217    * ++++++++++++++
218    * + Checksums  + <= Optional
219    * ++++++++++++++
220    * + Metadata!  +
221    * ++++++++++++++
222    * </code>
223    * @see #serialize(ByteBuffer)
224    */
225   static final CacheableDeserializer<Cacheable> BLOCK_DESERIALIZER =
226       new CacheableDeserializer<Cacheable>() {
227         @Override
228         public HFileBlock deserialize(ByteBuffer buf, boolean reuse) throws IOException{
229           // The buf has the file block followed by block metadata.
230           // Set limit to just before the BLOCK_METADATA_SPACE then rewind.
231           buf.limit(buf.limit() - BLOCK_METADATA_SPACE).rewind();
232           // Get a new buffer to pass the HFileBlock for it to 'own'.
233           ByteBuffer newByteBuff;
234           if (reuse) {
235             newByteBuff = buf.slice();
236           } else {
237             int len = buf.limit();
238             newByteBuff = ByteBuffer.allocate(len);
239             ByteBufferUtils.copyFromBufferToBuffer(newByteBuff, buf, buf.position(), 0, len);
240           }
241           // Read out the BLOCK_METADATA_SPACE content and shove into our HFileBlock.
242           buf.position(buf.limit());
243           buf.limit(buf.limit() + HFileBlock.BLOCK_METADATA_SPACE);
244           boolean usesChecksum = buf.get() == (byte)1;
245           long offset = buf.getLong();
246           int nextBlockOnDiskSize = buf.getInt();
247           HFileBlock hFileBlock =
248               new HFileBlock(newByteBuff, usesChecksum, offset, nextBlockOnDiskSize, null);
249           return hFileBlock;
250         }
251 
252         @Override
253         public int getDeserialiserIdentifier() {
254           return DESERIALIZER_IDENTIFIER;
255         }
256 
257         @Override
258         public HFileBlock deserialize(ByteBuffer b) throws IOException {
259           return deserialize(b, false);
260         }
261       };
262   private static final int DESERIALIZER_IDENTIFIER;
263   static {
264     DESERIALIZER_IDENTIFIER =
265         CacheableDeserializerIdManager.registerDeserializer(BLOCK_DESERIALIZER);
266   }
267 
268   // Todo: encapsulate Header related logic in this inner class.
269   static class Header {
270     // Format of header is:
271     // 8 bytes - block magic
272     // 4 bytes int - onDiskSizeWithoutHeader
273     // 4 bytes int - uncompressedSizeWithoutHeader
274     // 8 bytes long - prevBlockOffset
275     // The following 3 are only present if header contains checksum information
276     // 1 byte - checksum type
277     // 4 byte int - bytes per checksum
278     // 4 byte int - onDiskDataSizeWithHeader
279     static int BLOCK_MAGIC_INDEX = 0;
280     static int ON_DISK_SIZE_WITHOUT_HEADER_INDEX = 8;
281     static int UNCOMPRESSED_SIZE_WITHOUT_HEADER_INDEX = 12;
282     static int PREV_BLOCK_OFFSET_INDEX = 16;
283     static int CHECKSUM_TYPE_INDEX = 24;
284     static int BYTES_PER_CHECKSUM_INDEX = 25;
285     static int ON_DISK_DATA_SIZE_WITH_HEADER_INDEX = 29;
286   }
287 
288   /**
289    * Copy constructor. Creates a shallow copy of {@code that}'s buffer.
290    */
291   private HFileBlock(HFileBlock that) {
292     this.blockType = that.blockType;
293     this.onDiskSizeWithoutHeader = that.onDiskSizeWithoutHeader;
294     this.uncompressedSizeWithoutHeader = that.uncompressedSizeWithoutHeader;
295     this.prevBlockOffset = that.prevBlockOffset;
296     this.buf = that.buf.duplicate();
297     this.offset = that.offset;
298     this.onDiskDataSizeWithHeader = that.onDiskDataSizeWithHeader;
299     this.fileContext = that.fileContext;
300     this.nextBlockOnDiskSize = that.nextBlockOnDiskSize;
301   }
302 
303   /**
304    * Creates a new {@link HFile} block from the given fields. This constructor
305    * is used only while writing blocks and caching,
306    * and is sitting in a byte buffer and we want to stuff the block into cache.
307    * See {@link Writer#getBlockForCaching(CacheConfig)}.
308    *
309    * <p>TODO: The caller presumes no checksumming
310    * required of this block instance since going into cache; checksum already verified on
311    * underlying block data pulled in from filesystem. Is that correct? What if cache is SSD?
312    *
313    * @param blockType the type of this block, see {@link BlockType}
314    * @param onDiskSizeWithoutHeader see {@link #onDiskSizeWithoutHeader}
315    * @param uncompressedSizeWithoutHeader see {@link #uncompressedSizeWithoutHeader}
316    * @param prevBlockOffset see {@link #prevBlockOffset}
317    * @param b block header ({@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes)
318    * @param fillHeader when true, write the first 4 header fields into passed buffer.
319    * @param offset the file offset the block was read from
320    * @param onDiskDataSizeWithHeader see {@link #onDiskDataSizeWithHeader}
321    * @param fileContext HFile meta data
322    */
323   @VisibleForTesting
324   public HFileBlock(BlockType blockType, int onDiskSizeWithoutHeader,
325       int uncompressedSizeWithoutHeader, long prevBlockOffset, ByteBuffer b, boolean fillHeader,
326       long offset, final int nextBlockOnDiskSize, int onDiskDataSizeWithHeader,
327       HFileContext fileContext) {
328     init(blockType, onDiskSizeWithoutHeader, uncompressedSizeWithoutHeader,
329         prevBlockOffset, offset, onDiskDataSizeWithHeader, nextBlockOnDiskSize, fileContext);
330     this.buf = b;
331     if (fillHeader) {
332       overwriteHeader();
333     }
334     this.buf.rewind();
335   }
336 
337   /**
338    * Creates a block from an existing buffer starting with a header. Rewinds
339    * and takes ownership of the buffer. By definition of rewind, ignores the
340    * buffer position, but if you slice the buffer beforehand, it will rewind
341    * to that point.
342    * @param buf Has header, content, and trailing checksums if present.
343    */
344   HFileBlock(ByteBuffer buf, boolean usesHBaseChecksum, final long offset,
345       final int nextBlockOnDiskSize, HFileContext fileContext) throws IOException {
346     buf.rewind();
347     final BlockType blockType = BlockType.read(buf);
348     final int onDiskSizeWithoutHeader = buf.getInt(Header.ON_DISK_SIZE_WITHOUT_HEADER_INDEX);
349     final int uncompressedSizeWithoutHeader =
350         buf.getInt(Header.UNCOMPRESSED_SIZE_WITHOUT_HEADER_INDEX);
351     final long prevBlockOffset = buf.getLong(Header.PREV_BLOCK_OFFSET_INDEX);
352     // This constructor is called when we deserialize a block from cache and when we read a block in
353     // from the fs. fileCache is null when deserialized from cache so need to make up one.
354     HFileContextBuilder fileContextBuilder = fileContext != null?
355         new HFileContextBuilder(fileContext): new HFileContextBuilder();
356     fileContextBuilder.withHBaseCheckSum(usesHBaseChecksum);
357     int onDiskDataSizeWithHeader;
358     if (usesHBaseChecksum) {
359       byte checksumType = buf.get(Header.CHECKSUM_TYPE_INDEX);
360       int bytesPerChecksum = buf.getInt(Header.BYTES_PER_CHECKSUM_INDEX);
361       onDiskDataSizeWithHeader = buf.getInt(Header.ON_DISK_DATA_SIZE_WITH_HEADER_INDEX);
362       // Use the checksum type and bytes per checksum from header, not from filecontext.
363       fileContextBuilder.withChecksumType(ChecksumType.codeToType(checksumType));
364       fileContextBuilder.withBytesPerCheckSum(bytesPerChecksum);
365     } else {
366       fileContextBuilder.withChecksumType(ChecksumType.NULL);
367       fileContextBuilder.withBytesPerCheckSum(0);
368       // Need to fix onDiskDataSizeWithHeader; there are not checksums after-block-data
369       onDiskDataSizeWithHeader = onDiskSizeWithoutHeader + headerSize(usesHBaseChecksum);
370     }
371     fileContext = fileContextBuilder.build();
372     assert usesHBaseChecksum == fileContext.isUseHBaseChecksum();
373     init(blockType, onDiskSizeWithoutHeader, uncompressedSizeWithoutHeader,
374         prevBlockOffset, offset, onDiskDataSizeWithHeader, nextBlockOnDiskSize, fileContext);
375     this.offset = offset;
376     this.buf = buf;
377     this.buf.rewind();
378  }
379 
380   /**
381    * Called from constructors.
382    */
383   private void init(BlockType blockType, int onDiskSizeWithoutHeader,
384       int uncompressedSizeWithoutHeader, long prevBlockOffset,
385       long offset, int onDiskDataSizeWithHeader, final int nextBlockOnDiskSize,
386       HFileContext fileContext) {
387     this.blockType = blockType;
388     this.onDiskSizeWithoutHeader = onDiskSizeWithoutHeader;
389     this.uncompressedSizeWithoutHeader = uncompressedSizeWithoutHeader;
390     this.prevBlockOffset = prevBlockOffset;
391     this.offset = offset;
392     this.onDiskDataSizeWithHeader = onDiskDataSizeWithHeader;
393     this.nextBlockOnDiskSize = nextBlockOnDiskSize;
394     this.fileContext = fileContext;
395   }
396 
397   /**
398    * Parse total ondisk size including header and checksum.
399    * @param headerBuf Header ByteBuffer. Presumed exact size of header.
400    * @param verifyChecksum true if checksum verification is in use.
401    * @return Size of the block with header included.
402    */
403   private static int getOnDiskSizeWithHeader(final ByteBuffer headerBuf, boolean verifyChecksum) {
404     return headerBuf.getInt(Header.ON_DISK_SIZE_WITHOUT_HEADER_INDEX) +
405       headerSize(verifyChecksum);
406   }
407 
408   /**
409    * @return the on-disk size of the next block (including the header size and any checksums if
410    * present) read by peeking into the next block's header; use as a hint when doing
411    * a read of the next block when scanning or running over a file.
412    */
413   public int getNextBlockOnDiskSize() {
414     return nextBlockOnDiskSize;
415   }
416 
417   @Override
418   public BlockType getBlockType() {
419     return blockType;
420   }
421 
422   /** @return get data block encoding id that was used to encode this block */
423   public short getDataBlockEncodingId() {
424     if (blockType != BlockType.ENCODED_DATA) {
425       throw new IllegalArgumentException("Querying encoder ID of a block " +
426           "of type other than " + BlockType.ENCODED_DATA + ": " + blockType);
427     }
428     return buf.getShort(headerSize());
429   }
430 
431   /**
432    * @return the on-disk size of header + data part + checksum.
433    */
434   public int getOnDiskSizeWithHeader() {
435     return onDiskSizeWithoutHeader + headerSize();
436   }
437 
438   /**
439    * @return the on-disk size of the data part + checksum (header excluded).
440    */
441   int getOnDiskSizeWithoutHeader() {
442     return onDiskSizeWithoutHeader;
443   }
444 
445   /**
446    * @return the uncompressed size of data part (header and checksum excluded).
447    */
448   public int getUncompressedSizeWithoutHeader() {
449     return uncompressedSizeWithoutHeader;
450   }
451 
452   /**
453    * @return the offset of the previous block of the same type in the file, or
454    *         -1 if unknown
455    */
456   long getPrevBlockOffset() {
457     return prevBlockOffset;
458   }
459 
460   /**
461    * Rewinds {@code buf} and writes first 4 header fields. {@code buf} position
462    * is modified as side-effect.
463    */
464   private void overwriteHeader() {
465     buf.rewind();
466     blockType.write(buf);
467     buf.putInt(onDiskSizeWithoutHeader);
468     buf.putInt(uncompressedSizeWithoutHeader);
469     buf.putLong(prevBlockOffset);
470     if (this.fileContext.isUseHBaseChecksum()) {
471       buf.put(fileContext.getChecksumType().getCode());
472       buf.putInt(fileContext.getBytesPerChecksum());
473       buf.putInt(onDiskDataSizeWithHeader);
474     }
475   }
476 
477   /**
478    * Returns a buffer that does not include the header or checksum.
479    *
480    * @return the buffer with header skipped and checksum omitted.
481    */
482   public ByteBuffer getBufferWithoutHeader() {
483     ByteBuffer dup = getBufferReadOnly();
484     // Now set it up so Buffer spans content only -- no header or no checksums.
485     dup.position(headerSize()).limit(buf.limit() - totalChecksumBytes());
486     return dup.slice();
487   }
488 
489   /**
490    * Returns a read-only duplicate of the buffer this block stores internally ready to be read.
491    * Clients must not modify the buffer object though they may set position and limit on the
492    * returned buffer since we pass back a duplicate. This method has to be public because it is used
493    * used in {@link org.apache.hadoop.hbase.util.CompoundBloomFilter}
494    * to avoid object creation on every Bloom
495    * filter lookup, but has to be used with caution. Buffer holds header, block content,
496    * and any follow-on checksums if present.
497    *
498    * @return the buffer of this block for read-only operations
499    */
500   public ByteBuffer getBufferReadOnly() {
501     ByteBuffer dup = this.buf.duplicate();
502     return dup;
503   }
504 
505   private void sanityCheckAssertion(long valueFromBuf, long valueFromField,
506       String fieldName) throws IOException {
507     if (valueFromBuf != valueFromField) {
508       throw new AssertionError(fieldName + " in the buffer (" + valueFromBuf
509           + ") is different from that in the field (" + valueFromField + ")");
510     }
511   }
512 
513   private void sanityCheckAssertion(BlockType valueFromBuf, BlockType valueFromField)
514       throws IOException {
515     if (valueFromBuf != valueFromField) {
516       throw new IOException("Block type stored in the buffer: " +
517         valueFromBuf + ", block type field: " + valueFromField);
518     }
519   }
520 
521   /**
522    * Checks if the block is internally consistent, i.e. the first
523    * {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes of the buffer contain a
524    * valid header consistent with the fields. Assumes a packed block structure.
525    * This function is primary for testing and debugging, and is not
526    * thread-safe, because it alters the internal buffer pointer.
527    * Used by tests only.
528    */
529   @VisibleForTesting
530   void sanityCheck() throws IOException {
531     // Duplicate so no side-effects
532     ByteBuffer dup = this.buf.duplicate();
533     dup.rewind();
534     sanityCheckAssertion(BlockType.read(dup), blockType);
535     sanityCheckAssertion(dup.getInt(), onDiskSizeWithoutHeader, "onDiskSizeWithoutHeader");
536     sanityCheckAssertion(dup.getInt(), uncompressedSizeWithoutHeader,
537         "uncompressedSizeWithoutHeader");
538     sanityCheckAssertion(dup.getLong(), prevBlockOffset, "prevBlockOffset");
539     if (this.fileContext.isUseHBaseChecksum()) {
540       sanityCheckAssertion(dup.get(), this.fileContext.getChecksumType().getCode(), "checksumType");
541       sanityCheckAssertion(dup.getInt(), this.fileContext.getBytesPerChecksum(),
542           "bytesPerChecksum");
543       sanityCheckAssertion(dup.getInt(), onDiskDataSizeWithHeader, "onDiskDataSizeWithHeader");
544     }
545 
546     int cksumBytes = totalChecksumBytes();
547     int expectedBufLimit = onDiskDataSizeWithHeader + cksumBytes;
548     if (dup.limit() != expectedBufLimit) {
549       throw new AssertionError("Expected limit " + expectedBufLimit + ", got " + dup.limit());
550     }
551 
552     // We might optionally allocate HFILEBLOCK_HEADER_SIZE more bytes to read the next
553     // block's header, so there are two sensible values for buffer capacity.
554     int hdrSize = headerSize();
555     if (dup.capacity() != expectedBufLimit && dup.capacity() != expectedBufLimit + hdrSize) {
556       throw new AssertionError("Invalid buffer capacity: " + dup.capacity() +
557           ", expected " + expectedBufLimit + " or " + (expectedBufLimit + hdrSize));
558     }
559   }
560 
561   @Override
562   public String toString() {
563     StringBuilder sb = new StringBuilder()
564       .append("[")
565       .append("blockType=").append(blockType)
566       .append(", fileOffset=").append(offset)
567       .append(", headerSize=").append(headerSize())
568       .append(", onDiskSizeWithoutHeader=").append(onDiskSizeWithoutHeader)
569       .append(", uncompressedSizeWithoutHeader=").append(uncompressedSizeWithoutHeader)
570       .append(", prevBlockOffset=").append(prevBlockOffset)
571       .append(", isUseHBaseChecksum=").append(fileContext.isUseHBaseChecksum());
572     if (fileContext.isUseHBaseChecksum()) {
573       sb.append(", checksumType=").append(ChecksumType.codeToType(this.buf.get(24)))
574         .append(", bytesPerChecksum=").append(this.buf.getInt(24 + 1))
575         .append(", onDiskDataSizeWithHeader=").append(onDiskDataSizeWithHeader);
576     } else {
577       sb.append(", onDiskDataSizeWithHeader=").append(onDiskDataSizeWithHeader)
578         .append("(").append(onDiskSizeWithoutHeader)
579         .append("+").append(HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM).append(")");
580     }
581     String dataBegin = null;
582     if (buf.hasArray()) {
583       dataBegin = Bytes.toStringBinary(buf.array(), buf.arrayOffset() + headerSize(),
584           Math.min(32, buf.limit() - buf.arrayOffset() - headerSize()));
585     } else {
586       ByteBuffer bufWithoutHeader = getBufferWithoutHeader();
587       byte[] dataBeginBytes = new byte[Math.min(32,
588           bufWithoutHeader.limit() - bufWithoutHeader.position())];
589       bufWithoutHeader.get(dataBeginBytes);
590       dataBegin = Bytes.toStringBinary(dataBeginBytes);
591     }
592     sb.append(", getOnDiskSizeWithHeader=").append(getOnDiskSizeWithHeader())
593       .append(", totalChecksumBytes=").append(totalChecksumBytes())
594       .append(", isUnpacked=").append(isUnpacked())
595       .append(", buf=[").append(buf).append("]")
596       .append(", dataBeginsWith=").append(dataBegin)
597       .append(", fileContext=").append(fileContext)
598       .append(", nextBlockOnDiskSize=").append(nextBlockOnDiskSize)
599       .append("]");
600     return sb.toString();
601   }
602 
603   /**
604    * Retrieves the decompressed/decrypted view of this block. An encoded block remains in its
605    * encoded structure. Internal structures are shared between instances where applicable.
606    */
607   HFileBlock unpack(HFileContext fileContext, FSReader reader) throws IOException {
608     if (!fileContext.isCompressedOrEncrypted()) {
609       // TODO: cannot use our own fileContext here because HFileBlock(ByteBuffer, boolean),
610       // which is used for block serialization to L2 cache, does not preserve encoding and
611       // encryption details.
612       return this;
613     }
614 
615     HFileBlock unpacked = new HFileBlock(this);
616     unpacked.allocateBuffer(); // allocates space for the decompressed block
617 
618     HFileBlockDecodingContext ctx = blockType == BlockType.ENCODED_DATA ?
619       reader.getBlockDecodingContext() : reader.getDefaultBlockDecodingContext();
620 
621     ByteBuffer dup = this.buf.duplicate();
622     dup.position(this.headerSize());
623     dup = dup.slice();
624     ctx.prepareDecoding(unpacked.getOnDiskSizeWithoutHeader(),
625       unpacked.getUncompressedSizeWithoutHeader(), unpacked.getBufferWithoutHeader(),
626       dup);
627     return unpacked;
628   }
629 
630   /**
631    * Always allocates a new buffer of the correct size. Copies header bytes
632    * from the existing buffer. Does not change header fields.
633    * Reserve room to keep checksum bytes too.
634    */
635   private void allocateBuffer() {
636     int cksumBytes = totalChecksumBytes();
637     int headerSize = headerSize();
638     int capacityNeeded = headerSize + uncompressedSizeWithoutHeader + cksumBytes;
639 
640     // TODO we need consider allocating offheap here?
641     ByteBuffer newBuf = ByteBuffer.allocate(capacityNeeded);
642 
643     // Copy header bytes into newBuf.
644     // newBuf is HBB so no issue in calling array()
645     ByteBuffer dup = buf.duplicate();
646     dup.position(0);
647     dup.get(newBuf.array(), newBuf.arrayOffset(), headerSize);
648 
649     buf = newBuf;
650     // set limit to exclude next block's header
651     buf.limit(headerSize + uncompressedSizeWithoutHeader + cksumBytes);
652   }
653 
654   /**
655    * Return true when this block's buffer has been unpacked, false otherwise. Note this is a
656    * calculated heuristic, not tracked attribute of the block.
657    */
658   public boolean isUnpacked() {
659     final int cksumBytes = totalChecksumBytes();
660     final int headerSize = headerSize();
661     final int expectedCapacity = headerSize + uncompressedSizeWithoutHeader + cksumBytes;
662     final int bufCapacity = buf.capacity();
663     return bufCapacity == expectedCapacity || bufCapacity == expectedCapacity + headerSize;
664   }
665 
666   /** An additional sanity-check in case no compression or encryption is being used. */
667   public void sanityCheckUncompressedSize() throws IOException {
668     if (onDiskSizeWithoutHeader != uncompressedSizeWithoutHeader + totalChecksumBytes()) {
669       throw new IOException("Using no compression but "
670           + "onDiskSizeWithoutHeader=" + onDiskSizeWithoutHeader + ", "
671           + "uncompressedSizeWithoutHeader=" + uncompressedSizeWithoutHeader
672           + ", numChecksumbytes=" + totalChecksumBytes());
673     }
674   }
675 
676   /**
677    * Cannot be {@link #UNSET}. Must be a legitimate value. Used re-making the {@link CacheKey} when
678    * block is returned to the cache.
679    * @return the offset of this block in the file it was read from
680    */
681   long getOffset() {
682     if (offset < 0) {
683       throw new IllegalStateException("HFile block offset not initialized properly");
684     }
685     return offset;
686   }
687 
688   /**
689    * @return a byte stream reading the data + checksum of this block
690    */
691   public DataInputStream getByteStream() {
692     ByteBuffer dup = this.buf.duplicate();
693     dup.position(this.headerSize());
694     return new DataInputStream(new ByteBufferInputStream(dup));
695   }
696 
697   @Override
698   public long heapSize() {
699     long size = ClassSize.align(
700         ClassSize.OBJECT +
701         // Block type, byte buffer and meta references
702         3 * ClassSize.REFERENCE +
703         // On-disk size, uncompressed size, and next block's on-disk size
704         // bytePerChecksum and onDiskDataSize
705         4 * Bytes.SIZEOF_INT +
706         // This and previous block offset
707         2 * Bytes.SIZEOF_LONG +
708         // Heap size of the meta object. meta will be always not null.
709         fileContext.heapSize()
710     );
711 
712     if (buf != null) {
713       // Deep overhead of the byte buffer. Needs to be aligned separately.
714       size += ClassSize.align(buf.capacity() + BYTE_BUFFER_HEAP_SIZE);
715     }
716 
717     return ClassSize.align(size);
718   }
719 
720   /**
721    * Read from an input stream at least <code>necessaryLen</code> and if possible,
722    * <code>extraLen</code> also if available. Analogous to
723    * {@link IOUtils#readFully(InputStream, byte[], int, int)}, but specifies a
724    * number of "extra" bytes to also optionally read.
725    *
726    * @param in the input stream to read from
727    * @param buf the buffer to read into
728    * @param bufOffset the destination offset in the buffer
729    * @param necessaryLen the number of bytes that are absolutely necessary to read
730    * @param extraLen the number of extra bytes that would be nice to read
731    * @return true if succeeded reading the extra bytes
732    * @throws IOException if failed to read the necessary bytes
733    */
734   static boolean readWithExtra(InputStream in, byte[] buf,
735       int bufOffset, int necessaryLen, int extraLen) throws IOException {
736     int bytesRemaining = necessaryLen + extraLen;
737     while (bytesRemaining > 0) {
738       int ret = in.read(buf, bufOffset, bytesRemaining);
739       if (ret == -1 && bytesRemaining <= extraLen) {
740         // We could not read the "extra data", but that is OK.
741         break;
742       }
743       if (ret < 0) {
744         throw new IOException("Premature EOF from inputStream (read "
745             + "returned " + ret + ", was trying to read " + necessaryLen
746             + " necessary bytes and " + extraLen + " extra bytes, "
747             + "successfully read "
748             + (necessaryLen + extraLen - bytesRemaining));
749       }
750       bufOffset += ret;
751       bytesRemaining -= ret;
752     }
753     return bytesRemaining <= 0;
754   }
755 
756   /**
757    * Read from an input stream at least <code>necessaryLen</code> and if possible,
758    * <code>extraLen</code> also if available. Analogous to
759    * {@link IOUtils#readFully(InputStream, byte[], int, int)}, but uses
760    * positional read and specifies a number of "extra" bytes that would be
761    * desirable but not absolutely necessary to read.
762    *
763    * @param in the input stream to read from
764    * @param position the position within the stream from which to start reading
765    * @param buf the buffer to read into
766    * @param bufOffset the destination offset in the buffer
767    * @param necessaryLen the number of bytes that are absolutely necessary to
768    *     read
769    * @param extraLen the number of extra bytes that would be nice to read
770    * @return true if and only if extraLen is > 0 and reading those extra bytes
771    *     was successful
772    * @throws IOException if failed to read the necessary bytes
773    */
774   @VisibleForTesting
775   static boolean positionalReadWithExtra(FSDataInputStream in,
776       long position, byte[] buf, int bufOffset, int necessaryLen, int extraLen)
777       throws IOException {
778     int bytesRemaining = necessaryLen + extraLen;
779     int bytesRead = 0;
780     while (bytesRead < necessaryLen) {
781       int ret = in.read(position, buf, bufOffset, bytesRemaining);
782       if (ret < 0) {
783         throw new IOException("Premature EOF from inputStream (positional read "
784             + "returned " + ret + ", was trying to read " + necessaryLen
785             + " necessary bytes and " + extraLen + " extra bytes, "
786             + "successfully read " + bytesRead);
787       }
788       position += ret;
789       bufOffset += ret;
790       bytesRemaining -= ret;
791       bytesRead += ret;
792     }
793     return bytesRead != necessaryLen && bytesRemaining <= 0;
794   }
795 
796   /**
797    * Unified version 2 {@link HFile} block writer. The intended usage pattern
798    * is as follows:
799    * <ol>
800    * <li>Construct an {@link HFileBlock.Writer}, providing a compression algorithm.
801    * <li>Call {@link Writer#startWriting} and get a data stream to write to.
802    * <li>Write your data into the stream.
803    * <li>Call Writer#writeHeaderAndData(FSDataOutputStream) as many times as you need to.
804    * store the serialized block into an external stream.
805    * <li>Repeat to write more blocks.
806    * </ol>
807    * <p>
808    */
809   public static class Writer {
810     private enum State {
811       INIT,
812       WRITING,
813       BLOCK_READY
814     };
815 
816     /** Writer state. Used to ensure the correct usage protocol. */
817     private State state = State.INIT;
818 
819     /** Data block encoder used for data blocks */
820     private final HFileDataBlockEncoder dataBlockEncoder;
821 
822     private HFileBlockEncodingContext dataBlockEncodingCtx;
823 
824     /** block encoding context for non-data blocks*/
825     private HFileBlockDefaultEncodingContext defaultBlockEncodingCtx;
826 
827     /**
828      * The stream we use to accumulate data into a block in an uncompressed format.
829      * We reset this stream at the end of each block and reuse it. The
830      * header is written as the first {@link HConstants#HFILEBLOCK_HEADER_SIZE} bytes into this
831      * stream.
832      */
833     private ByteArrayOutputStream baosInMemory;
834 
835     /**
836      * Current block type. Set in {@link #startWriting(BlockType)}. Could be
837      * changed in {@link #finishBlock()} from {@link BlockType#DATA}
838      * to {@link BlockType#ENCODED_DATA}.
839      */
840     private BlockType blockType;
841 
842     /**
843      * A stream that we write uncompressed bytes to, which compresses them and
844      * writes them to {@link #baosInMemory}.
845      */
846     private DataOutputStream userDataStream;
847 
848     // Size of actual data being written. Not considering the block encoding/compression. This
849     // includes the header size also.
850     private int unencodedDataSizeWritten;
851 
852     // Size of actual data being written. considering the block encoding. This
853     // includes the header size also.
854     private int encodedDataSizeWritten;
855 
856     /**
857      * Bytes to be written to the file system, including the header. Compressed
858      * if compression is turned on. It also includes the checksum data that
859      * immediately follows the block data. (header + data + checksums)
860      */
861     private ByteArrayOutputStream onDiskBlockBytesWithHeader;
862 
863     /**
864      * The size of the checksum data on disk. It is used only if data is
865      * not compressed. If data is compressed, then the checksums are already
866      * part of onDiskBytesWithHeader. If data is uncompressed, then this
867      * variable stores the checksum data for this block.
868      */
869     private byte[] onDiskChecksum = HConstants.EMPTY_BYTE_ARRAY;
870 
871     /**
872      * Current block's start offset in the {@link HFile}. Set in
873      * {@link #writeHeaderAndData(FSDataOutputStream)}.
874      */
875     private long startOffset;
876 
877     /**
878      * Offset of previous block by block type. Updated when the next block is
879      * started.
880      */
881     private long[] prevOffsetByType;
882 
883     /** The offset of the previous block of the same type */
884     private long prevOffset;
885     /** Meta data that holds information about the hfileblock**/
886     private HFileContext fileContext;
887 
888     /**
889      * @param dataBlockEncoder data block encoding algorithm to use
890      */
891     public Writer(HFileDataBlockEncoder dataBlockEncoder, HFileContext fileContext) {
892       if (fileContext.getBytesPerChecksum() < HConstants.HFILEBLOCK_HEADER_SIZE) {
893         throw new RuntimeException("Unsupported value of bytesPerChecksum. " +
894             " Minimum is " + HConstants.HFILEBLOCK_HEADER_SIZE + " but the configured value is " +
895             fileContext.getBytesPerChecksum());
896       }
897       this.dataBlockEncoder = dataBlockEncoder != null?
898           dataBlockEncoder: NoOpDataBlockEncoder.INSTANCE;
899       this.dataBlockEncodingCtx = this.dataBlockEncoder.
900           newDataBlockEncodingContext(HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext);
901       // TODO: This should be lazily instantiated since we usually do NOT need this default encoder
902       this.defaultBlockEncodingCtx = new HFileBlockDefaultEncodingContext(null,
903           HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext);
904       // TODO: Set BAOS initial size. Use fileContext.getBlocksize() and add for header/checksum
905       baosInMemory = new ByteArrayOutputStream();
906       prevOffsetByType = new long[BlockType.values().length];
907       for (int i = 0; i < prevOffsetByType.length; ++i) {
908         prevOffsetByType[i] = UNSET;
909       }
910       // TODO: Why fileContext saved away when we have dataBlockEncoder and/or
911       // defaultDataBlockEncoder?
912       this.fileContext = fileContext;
913     }
914 
915     /**
916      * Starts writing into the block. The previous block's data is discarded.
917      *
918      * @return the stream the user can write their data into
919      * @throws IOException
920      */
921     DataOutputStream startWriting(BlockType newBlockType)
922         throws IOException {
923       if (state == State.BLOCK_READY && startOffset != -1) {
924         // We had a previous block that was written to a stream at a specific
925         // offset. Save that offset as the last offset of a block of that type.
926         prevOffsetByType[blockType.getId()] = startOffset;
927       }
928 
929       startOffset = -1;
930       blockType = newBlockType;
931 
932       baosInMemory.reset();
933       baosInMemory.write(HConstants.HFILEBLOCK_DUMMY_HEADER);
934 
935       state = State.WRITING;
936 
937       // We will compress it later in finishBlock()
938       userDataStream = new DataOutputStream(baosInMemory);
939       if (newBlockType == BlockType.DATA) {
940         this.dataBlockEncoder.startBlockEncoding(dataBlockEncodingCtx, userDataStream);
941       }
942       this.unencodedDataSizeWritten = 0;
943       this.encodedDataSizeWritten = 0;
944       return userDataStream;
945     }
946 
947     /**
948      * Writes the Cell to this block
949      * @param cell
950      * @throws IOException
951      */
952     void write(Cell cell) throws IOException{
953       expectState(State.WRITING);
954       int posBeforeEncode = this.userDataStream.size();
955       this.unencodedDataSizeWritten +=
956           this.dataBlockEncoder.encode(cell, dataBlockEncodingCtx, this.userDataStream);
957       this.encodedDataSizeWritten += this.userDataStream.size() - posBeforeEncode;
958     }
959 
960     /**
961      * Returns the stream for the user to write to. The block writer takes care
962      * of handling compression and buffering for caching on write. Can only be
963      * called in the "writing" state.
964      *
965      * @return the data output stream for the user to write to
966      */
967     DataOutputStream getUserDataStream() {
968       expectState(State.WRITING);
969       return userDataStream;
970     }
971 
972     /**
973      * Transitions the block writer from the "writing" state to the "block
974      * ready" state.  Does nothing if a block is already finished.
975      */
976     void ensureBlockReady() throws IOException {
977       Preconditions.checkState(state != State.INIT,
978           "Unexpected state: " + state);
979 
980       if (state == State.BLOCK_READY) {
981         return;
982       }
983 
984       // This will set state to BLOCK_READY.
985       finishBlock();
986     }
987 
988     /**
989      * Finish up writing of the block.
990      * Flushes the compressing stream (if using compression), fills out the header,
991      * does any compression/encryption of bytes to flush out to disk, and manages
992      * the cache on write content, if applicable. Sets block write state to "block ready".
993      */
994     private void finishBlock() throws IOException {
995       if (blockType == BlockType.DATA) {
996         this.dataBlockEncoder.endBlockEncoding(dataBlockEncodingCtx, userDataStream,
997             baosInMemory.getBuffer(), blockType);
998         blockType = dataBlockEncodingCtx.getBlockType();
999       }
1000       userDataStream.flush();
1001       prevOffset = prevOffsetByType[blockType.getId()];
1002 
1003       // We need to set state before we can package the block up for cache-on-write. In a way, the
1004       // block is ready, but not yet encoded or compressed.
1005       state = State.BLOCK_READY;
1006       Bytes compressAndEncryptDat;
1007       if (blockType == BlockType.DATA || blockType == BlockType.ENCODED_DATA) {
1008         compressAndEncryptDat = dataBlockEncodingCtx.
1009             compressAndEncrypt(baosInMemory.getBuffer(),
1010                     0, baosInMemory.size());
1011       } else {
1012         compressAndEncryptDat = defaultBlockEncodingCtx.
1013             compressAndEncrypt(baosInMemory.getBuffer(),
1014                     0, baosInMemory.size());
1015       }
1016       if (compressAndEncryptDat == null) {
1017         compressAndEncryptDat = new Bytes(baosInMemory.getBuffer(),
1018           0, baosInMemory.size());
1019       }
1020       if (onDiskBlockBytesWithHeader == null) {
1021         onDiskBlockBytesWithHeader = new ByteArrayOutputStream(compressAndEncryptDat.getLength());
1022       }
1023       onDiskBlockBytesWithHeader.reset();
1024       onDiskBlockBytesWithHeader.write(compressAndEncryptDat.get(),
1025             compressAndEncryptDat.getOffset(), compressAndEncryptDat.getLength());
1026       // Calculate how many bytes we need for checksum on the tail of the block.
1027       int numBytes = (int) ChecksumUtil.numBytes(
1028           onDiskBlockBytesWithHeader.size(),
1029           fileContext.getBytesPerChecksum());
1030 
1031       // Put the header for the on disk bytes; header currently is unfilled-out
1032       putHeader(onDiskBlockBytesWithHeader,
1033           onDiskBlockBytesWithHeader.size() + numBytes,
1034           baosInMemory.size(), onDiskBlockBytesWithHeader.size());
1035       // Set the header for the uncompressed bytes (for cache-on-write)
1036       if (onDiskChecksum.length != numBytes) {
1037         onDiskChecksum = new byte[numBytes];
1038       }
1039       ChecksumUtil.generateChecksums(
1040           onDiskBlockBytesWithHeader.getBuffer(), 0,onDiskBlockBytesWithHeader.size(),
1041           onDiskChecksum, 0, fileContext.getChecksumType(), fileContext.getBytesPerChecksum());
1042     }
1043     private void putHeader(ByteArrayOutputStream dest, int onDiskSize,
1044       int uncompressedSize, int onDiskDataSize) {
1045       putHeader(dest.getBuffer(),0, onDiskSize, uncompressedSize, onDiskDataSize);
1046     }
1047     /**
1048      * Put the header into the given byte array at the given offset.
1049      * @param onDiskSize size of the block on disk header + data + checksum
1050      * @param uncompressedSize size of the block after decompression (but
1051      *          before optional data block decoding) including header
1052      * @param onDiskDataSize size of the block on disk with header
1053      *        and data but not including the checksums
1054      */
1055     private void putHeader(byte[] dest, int offset, int onDiskSize,
1056         int uncompressedSize, int onDiskDataSize) {
1057       offset = blockType.put(dest, offset);
1058       offset = Bytes.putInt(dest, offset, onDiskSize - HConstants.HFILEBLOCK_HEADER_SIZE);
1059       offset = Bytes.putInt(dest, offset, uncompressedSize - HConstants.HFILEBLOCK_HEADER_SIZE);
1060       offset = Bytes.putLong(dest, offset, prevOffset);
1061       offset = Bytes.putByte(dest, offset, fileContext.getChecksumType().getCode());
1062       offset = Bytes.putInt(dest, offset, fileContext.getBytesPerChecksum());
1063       Bytes.putInt(dest, offset, onDiskDataSize);
1064     }
1065 
1066     /**
1067      * Similar to {@link #writeHeaderAndData(FSDataOutputStream)}, but records
1068      * the offset of this block so that it can be referenced in the next block
1069      * of the same type.
1070      *
1071      * @param out
1072      * @throws IOException
1073      */
1074     void writeHeaderAndData(FSDataOutputStream out) throws IOException {
1075       long offset = out.getPos();
1076       if (startOffset != UNSET && offset != startOffset) {
1077         throw new IOException("A " + blockType + " block written to a "
1078             + "stream twice, first at offset " + startOffset + ", then at "
1079             + offset);
1080       }
1081       startOffset = offset;
1082 
1083       finishBlockAndWriteHeaderAndData((DataOutputStream) out);
1084     }
1085 
1086     /**
1087      * Writes the header and the compressed data of this block (or uncompressed
1088      * data when not using compression) into the given stream. Can be called in
1089      * the "writing" state or in the "block ready" state. If called in the
1090      * "writing" state, transitions the writer to the "block ready" state.
1091      *
1092      * @param out the output stream to write the
1093      * @throws IOException
1094      */
1095     protected void finishBlockAndWriteHeaderAndData(DataOutputStream out)
1096       throws IOException {
1097       ensureBlockReady();
1098       long startTime = System.currentTimeMillis();
1099       out.write(onDiskBlockBytesWithHeader.getBuffer(), 0, onDiskBlockBytesWithHeader.size());
1100       out.write(onDiskChecksum);
1101       HFile.updateWriteLatency(System.currentTimeMillis() - startTime);
1102     }
1103 
1104     /**
1105      * Returns the header or the compressed data (or uncompressed data when not
1106      * using compression) as a byte array. Can be called in the "writing" state
1107      * or in the "block ready" state. If called in the "writing" state,
1108      * transitions the writer to the "block ready" state. This returns
1109      * the header + data + checksums stored on disk.
1110      *
1111      * @return header and data as they would be stored on disk in a byte array
1112      * @throws IOException
1113      */
1114     byte[] getHeaderAndDataForTest() throws IOException {
1115       ensureBlockReady();
1116       // This is not very optimal, because we are doing an extra copy.
1117       // But this method is used only by unit tests.
1118       byte[] output =
1119           new byte[onDiskBlockBytesWithHeader.size()
1120               + onDiskChecksum.length];
1121       System.arraycopy(onDiskBlockBytesWithHeader.getBuffer(), 0, output, 0,
1122           onDiskBlockBytesWithHeader.size());
1123       System.arraycopy(onDiskChecksum, 0, output,
1124           onDiskBlockBytesWithHeader.size(), onDiskChecksum.length);
1125       return output;
1126     }
1127 
1128     /**
1129      * Releases resources used by this writer.
1130      */
1131     void release() {
1132       if (dataBlockEncodingCtx != null) {
1133         dataBlockEncodingCtx.close();
1134         dataBlockEncodingCtx = null;
1135       }
1136       if (defaultBlockEncodingCtx != null) {
1137         defaultBlockEncodingCtx.close();
1138         defaultBlockEncodingCtx = null;
1139       }
1140     }
1141 
1142     /**
1143      * Returns the on-disk size of the data portion of the block. This is the
1144      * compressed size if compression is enabled. Can only be called in the
1145      * "block ready" state. Header is not compressed, and its size is not
1146      * included in the return value.
1147      *
1148      * @return the on-disk size of the block, not including the header.
1149      */
1150     int getOnDiskSizeWithoutHeader() {
1151       expectState(State.BLOCK_READY);
1152       return onDiskBlockBytesWithHeader.size() +
1153           onDiskChecksum.length - HConstants.HFILEBLOCK_HEADER_SIZE;
1154     }
1155 
1156     /**
1157      * Returns the on-disk size of the block. Can only be called in the
1158      * "block ready" state.
1159      *
1160      * @return the on-disk size of the block ready to be written, including the
1161      *         header size, the data and the checksum data.
1162      */
1163     int getOnDiskSizeWithHeader() {
1164       expectState(State.BLOCK_READY);
1165       return onDiskBlockBytesWithHeader.size() + onDiskChecksum.length;
1166     }
1167 
1168     /**
1169      * The uncompressed size of the block data. Does not include header size.
1170      */
1171     int getUncompressedSizeWithoutHeader() {
1172       expectState(State.BLOCK_READY);
1173       return baosInMemory.size() - HConstants.HFILEBLOCK_HEADER_SIZE;
1174     }
1175 
1176     /**
1177      * The uncompressed size of the block data, including header size.
1178      */
1179     int getUncompressedSizeWithHeader() {
1180       expectState(State.BLOCK_READY);
1181       return baosInMemory.size();
1182     }
1183 
1184     /** @return true if a block is being written  */
1185     boolean isWriting() {
1186       return state == State.WRITING;
1187     }
1188 
1189     /**
1190      * Returns the number of bytes written into the current block so far, or
1191      * zero if not writing the block at the moment. Note that this will return
1192      * zero in the "block ready" state as well.
1193      *
1194      * @return the number of bytes written
1195      */
1196     public int encodedBlockSizeWritten() {
1197       if (state != State.WRITING)
1198         return 0;
1199       return this.encodedDataSizeWritten;
1200     }
1201 
1202     /**
1203      * Returns the number of bytes written into the current block so far, or
1204      * zero if not writing the block at the moment. Note that this will return
1205      * zero in the "block ready" state as well.
1206      *
1207      * @return the number of bytes written
1208      */
1209     int blockSizeWritten() {
1210       if (state != State.WRITING) return 0;
1211       return this.unencodedDataSizeWritten;
1212     }
1213 
1214     /**
1215      * Clones the header followed by the uncompressed data, even if using
1216      * compression. This is needed for storing uncompressed blocks in the block
1217      * cache. Can be called in the "writing" state or the "block ready" state.
1218      * Returns only the header and data, does not include checksum data.
1219      *
1220      * @return Returns a copy of uncompressed block bytes for caching on write
1221      */
1222     @VisibleForTesting
1223     ByteBuffer cloneUncompressedBufferWithHeader() {
1224       expectState(State.BLOCK_READY);
1225       byte[] uncompressedBlockBytesWithHeader = baosInMemory.toByteArray();
1226       int numBytes = (int) ChecksumUtil.numBytes(
1227           onDiskBlockBytesWithHeader.size(),
1228           fileContext.getBytesPerChecksum());
1229       putHeader(uncompressedBlockBytesWithHeader, 0,
1230         onDiskBlockBytesWithHeader.size() + numBytes,
1231         uncompressedBlockBytesWithHeader.length, onDiskBlockBytesWithHeader.size());
1232       return ByteBuffer.wrap(uncompressedBlockBytesWithHeader);
1233     }
1234 
1235     /**
1236      * Clones the header followed by the on-disk (compressed/encoded/encrypted) data. This is
1237      * needed for storing packed blocks in the block cache. Expects calling semantics identical to
1238      * {@link #getUncompressedBufferWithHeader()}. Returns only the header and data,
1239      * Does not include checksum data.
1240      *
1241      * @return Returns a copy of block bytes for caching on write
1242      */
1243     private ByteBuffer cloneOnDiskBufferWithHeader() {
1244       expectState(State.BLOCK_READY);
1245       return ByteBuffer.wrap(onDiskBlockBytesWithHeader.toByteArray());
1246     }
1247 
1248     private void expectState(State expectedState) {
1249       if (state != expectedState) {
1250         throw new IllegalStateException("Expected state: " + expectedState +
1251             ", actual state: " + state);
1252       }
1253     }
1254 
1255     /**
1256      * Takes the given {@link BlockWritable} instance, creates a new block of
1257      * its appropriate type, writes the writable into this block, and flushes
1258      * the block into the output stream. The writer is instructed not to buffer
1259      * uncompressed bytes for cache-on-write.
1260      *
1261      * @param bw the block-writable object to write as a block
1262      * @param out the file system output stream
1263      * @throws IOException
1264      */
1265     void writeBlock(BlockWritable bw, FSDataOutputStream out)
1266         throws IOException {
1267       bw.writeToBlock(startWriting(bw.getBlockType()));
1268       writeHeaderAndData(out);
1269     }
1270 
1271     /**
1272      * Creates a new HFileBlock. Checksums have already been validated, so
1273      * the byte buffer passed into the constructor of this newly created
1274      * block does not have checksum data even though the header minor
1275      * version is MINOR_VERSION_WITH_CHECKSUM. This is indicated by setting a
1276      * 0 value in bytesPerChecksum. This method copies the on-disk or
1277      * uncompressed data to build the HFileBlock which is used only
1278      * while writing blocks and caching.
1279      *
1280      * <p>TODO: Should there be an option where a cache can ask that hbase preserve block
1281      * checksums for checking after a block comes out of the cache? Otehrwise, cache is responsible
1282      * for blocks being wholesome (ECC memory or if file-backed, it does checksumming).
1283      */
1284     HFileBlock getBlockForCaching(CacheConfig cacheConf) {
1285       HFileContext newContext = new HFileContextBuilder()
1286                                 .withBlockSize(fileContext.getBlocksize())
1287                                 .withBytesPerCheckSum(0)
1288                                 .withChecksumType(ChecksumType.NULL) // no checksums in cached data
1289                                 .withCompression(fileContext.getCompression())
1290                                 .withDataBlockEncoding(fileContext.getDataBlockEncoding())
1291                                 .withHBaseCheckSum(fileContext.isUseHBaseChecksum())
1292                                 .withCompressTags(fileContext.isCompressTags())
1293                                 .withIncludesMvcc(fileContext.isIncludesMvcc())
1294                                 .withIncludesTags(fileContext.isIncludesTags())
1295                                 .build();
1296        return new HFileBlock(blockType, getOnDiskSizeWithoutHeader(),
1297           getUncompressedSizeWithoutHeader(), prevOffset,
1298           cacheConf.shouldCacheCompressed(blockType.getCategory())?
1299             cloneOnDiskBufferWithHeader() :
1300             cloneUncompressedBufferWithHeader(),
1301           FILL_HEADER, startOffset, UNSET,
1302           onDiskBlockBytesWithHeader.size() + onDiskChecksum.length, newContext);
1303     }
1304   }
1305 
1306   /** Something that can be written into a block. */
1307   interface BlockWritable {
1308 
1309     /** The type of block this data should use. */
1310     BlockType getBlockType();
1311 
1312     /**
1313      * Writes the block to the provided stream. Must not write any magic
1314      * records.
1315      *
1316      * @param out a stream to write uncompressed data into
1317      */
1318     void writeToBlock(DataOutput out) throws IOException;
1319   }
1320 
1321   // Block readers and writers
1322 
1323   /** An interface allowing to iterate {@link HFileBlock}s. */
1324   interface BlockIterator {
1325 
1326     /**
1327      * Get the next block, or null if there are no more blocks to iterate.
1328      */
1329     HFileBlock nextBlock() throws IOException;
1330 
1331     /**
1332      * Similar to {@link #nextBlock()} but checks block type, throws an
1333      * exception if incorrect, and returns the HFile block
1334      */
1335     HFileBlock nextBlockWithBlockType(BlockType blockType) throws IOException;
1336   }
1337 
1338   /** A full-fledged reader with iteration ability. */
1339   interface FSReader {
1340 
1341     /**
1342      * Reads the block at the given offset in the file with the given on-disk
1343      * size and uncompressed size.
1344      *
1345      * @param offset
1346      * @param onDiskSize the on-disk size of the entire block, including all
1347      *          applicable headers, or -1 if unknown
1348      * @return the newly read block
1349      */
1350     HFileBlock readBlockData(long offset, long onDiskSize, boolean pread, boolean updateMetrics)
1351         throws IOException;
1352 
1353     /**
1354      * Creates a block iterator over the given portion of the {@link HFile}.
1355      * The iterator returns blocks starting with offset such that offset &lt;=
1356      * startOffset &lt; endOffset. Returned blocks are always unpacked.
1357      *
1358      * @param startOffset the offset of the block to start iteration with
1359      * @param endOffset the offset to end iteration at (exclusive)
1360      * @return an iterator of blocks between the two given offsets
1361      */
1362     BlockIterator blockRange(long startOffset, long endOffset);
1363 
1364     /** Closes the backing streams */
1365     void closeStreams() throws IOException;
1366 
1367     /** Get a decoder for {@link BlockType#ENCODED_DATA} blocks from this file. */
1368     HFileBlockDecodingContext getBlockDecodingContext();
1369 
1370     /** Get the default decoder for blocks from this file. */
1371     HFileBlockDecodingContext getDefaultBlockDecodingContext();
1372 
1373     /**
1374      * To close the stream's socket. Note: This can be concurrently called from multiple threads and
1375      * implementation should take care of thread safety.
1376      */
1377     void unbufferStream();
1378   }
1379 
1380   /**
1381    * A common implementation of some methods of {@link FSReader} and some
1382    * tools for implementing HFile format version-specific block readers.
1383    */
1384   private abstract static class AbstractFSReader implements FSReader {
1385     /** Compression algorithm used by the {@link HFile} */
1386 
1387     /** The size of the file we are reading from, or -1 if unknown. */
1388     protected long fileSize;
1389 
1390     /** The size of the header */
1391     protected final int hdrSize;
1392 
1393     /** The filesystem used to access data */
1394     protected HFileSystem hfs;
1395 
1396     protected final Lock streamLock = new ReentrantLock();
1397 
1398     /** The default buffer size for our buffered streams */
1399     public static final int DEFAULT_BUFFER_SIZE = 1 << 20;
1400 
1401     protected HFileContext fileContext;
1402     // Cache the fileName
1403     protected String pathName;
1404 
1405     public AbstractFSReader(long fileSize, HFileSystem hfs, Path path, HFileContext fileContext)
1406         throws IOException {
1407       this.fileSize = fileSize;
1408       this.hfs = hfs;
1409       if (path != null) {
1410         this.pathName = path.toString();
1411       }
1412       this.fileContext = fileContext;
1413       this.hdrSize = headerSize(fileContext.isUseHBaseChecksum());
1414     }
1415 
1416     @Override
1417     public BlockIterator blockRange(final long startOffset,
1418         final long endOffset) {
1419       final FSReader owner = this; // handle for inner class
1420       return new BlockIterator() {
1421         private long offset = startOffset;
1422         // Cache length of next block. Current block has the length of next block in it.
1423         private long length = -1;
1424 
1425         @Override
1426         public HFileBlock nextBlock() throws IOException {
1427           if (offset >= endOffset) {
1428             return null;
1429           }
1430           HFileBlock b = readBlockData(offset, length, false, false);
1431           offset += b.getOnDiskSizeWithHeader();
1432           length = b.getNextBlockOnDiskSize();
1433           return b.unpack(fileContext, owner);
1434         }
1435 
1436         @Override
1437         public HFileBlock nextBlockWithBlockType(BlockType blockType)
1438             throws IOException {
1439           HFileBlock blk = nextBlock();
1440           if (blk.getBlockType() != blockType) {
1441             throw new IOException("Expected block of type " + blockType
1442                 + " but found " + blk.getBlockType());
1443           }
1444           return blk;
1445         }
1446       };
1447     }
1448 
1449     /**
1450      * Does a positional read or a seek and read into the given buffer. Returns
1451      * the on-disk size of the next block, or -1 if it could not be read/determined; e.g. EOF.
1452      *
1453      * @param dest destination buffer
1454      * @param destOffset offset into the destination buffer at where to put the bytes we read
1455      * @param size size of read
1456      * @param peekIntoNextBlock whether to read the next block's on-disk size
1457      * @param fileOffset position in the stream to read at
1458      * @param pread whether we should do a positional read
1459      * @param istream The input source of data
1460      * @return the on-disk size of the next block with header size included, or
1461      *         -1 if it could not be determined; if not -1, the <code>dest</code> INCLUDES the
1462      *         next header
1463      * @throws IOException
1464      */
1465     protected int readAtOffset(FSDataInputStream istream, byte [] dest, int destOffset, int size,
1466         boolean peekIntoNextBlock, long fileOffset, boolean pread) throws IOException {
1467       if (peekIntoNextBlock && destOffset + size + hdrSize > dest.length) {
1468         // We are asked to read the next block's header as well, but there is
1469         // not enough room in the array.
1470         throw new IOException("Attempted to read " + size + " bytes and " +
1471             hdrSize + " bytes of next header into a " + dest.length +
1472             "-byte array at offset " + destOffset);
1473       }
1474 
1475       if (!pread && streamLock.tryLock()) {
1476         // Seek + read. Better for scanning.
1477         try {
1478           HFileUtil.seekOnMultipleSources(istream, fileOffset);
1479           // TODO: do we need seek time latencies?
1480           long realOffset = istream.getPos();
1481           if (realOffset != fileOffset) {
1482             throw new IOException("Tried to seek to " + fileOffset + " to "
1483                 + "read " + size + " bytes, but pos=" + realOffset
1484                 + " after seek");
1485           }
1486 
1487           if (!peekIntoNextBlock) {
1488             IOUtils.readFully(istream, dest, destOffset, size);
1489             return -1;
1490           }
1491 
1492           // Try to read the next block header.
1493           if (!readWithExtra(istream, dest, destOffset, size, hdrSize)) {
1494             return -1;
1495           }
1496         } finally {
1497           streamLock.unlock();
1498         }
1499       } else {
1500         // Positional read. Better for random reads; or when the streamLock is already locked.
1501         int extraSize = peekIntoNextBlock ? hdrSize : 0;
1502         if (!positionalReadWithExtra(istream, fileOffset, dest, destOffset, size, extraSize)) {
1503           return -1;
1504         }
1505       }
1506 
1507       assert peekIntoNextBlock;
1508       return Bytes.toInt(dest, destOffset + size + BlockType.MAGIC_LENGTH) + hdrSize;
1509     }
1510 
1511   }
1512 
1513   /**
1514    * Data-structure to use caching the header of the NEXT block. Only works if next read
1515    * that comes in here is next in sequence in this block.
1516    *
1517    * When we read, we read current block and the next blocks' header. We do this so we have
1518    * the length of the next block to read if the hfile index is not available (rare).
1519    * TODO: Review!! This trick of reading next blocks header is a pain, complicates our
1520    * read path and I don't think it needed given it rare we don't have the block index
1521    * (it is 'normally' present, gotten from the hfile index). FIX!!!
1522   */
1523   private static class PrefetchedHeader {
1524     long offset = -1;
1525     byte [] header = new byte[HConstants.HFILEBLOCK_HEADER_SIZE];
1526     final ByteBuffer buf = ByteBuffer.wrap(header, 0, HConstants.HFILEBLOCK_HEADER_SIZE);
1527 
1528     @Override
1529     public String toString() {
1530       return "offset=" + this.offset + ", header=" + Bytes.toStringBinary(header);
1531     }
1532   }
1533 
1534   /**
1535    * Reads version 2 blocks from the filesystem.
1536    */
1537   static class FSReaderImpl extends AbstractFSReader {
1538     /** The file system stream of the underlying {@link HFile} that
1539      * does or doesn't do checksum validations in the filesystem */
1540     protected FSDataInputStreamWrapper streamWrapper;
1541 
1542     private HFileBlockDecodingContext encodedBlockDecodingCtx;
1543 
1544     /** Default context used when BlockType != {@link BlockType#ENCODED_DATA}. */
1545     private final HFileBlockDefaultDecodingContext defaultDecodingCtx;
1546 
1547     /**
1548      * Cache of the NEXT header after this. Check it is indeed next blocks header
1549      * before using it. TODO: Review. This overread into next block to fetch
1550      * next blocks header seems unnecessary given we usually get the block size
1551      * from the hfile index. Review!
1552      */
1553     private AtomicReference<PrefetchedHeader> prefetchedHeader =
1554       new AtomicReference<PrefetchedHeader>(new PrefetchedHeader());
1555 
1556     public FSReaderImpl(FSDataInputStreamWrapper stream, long fileSize, HFileSystem hfs, Path path,
1557         HFileContext fileContext) throws IOException {
1558       super(fileSize, hfs, path, fileContext);
1559       this.streamWrapper = stream;
1560       // Older versions of HBase didn't support checksum.
1561       this.streamWrapper.prepareForBlockReader(!fileContext.isUseHBaseChecksum());
1562       defaultDecodingCtx = new HFileBlockDefaultDecodingContext(fileContext);
1563       encodedBlockDecodingCtx = defaultDecodingCtx;
1564     }
1565 
1566     /**
1567      * A constructor that reads files with the latest minor version.
1568      * This is used by unit tests only.
1569      */
1570     FSReaderImpl(FSDataInputStream istream, long fileSize, HFileContext fileContext)
1571     throws IOException {
1572       this(new FSDataInputStreamWrapper(istream), fileSize, null, null, fileContext);
1573     }
1574 
1575     /**
1576      * Reads a version 2 block (version 1 blocks not supported and not expected). Tries to do as
1577      * little memory allocation as possible, using the provided on-disk size.
1578      *
1579      * @param offset the offset in the stream to read at
1580      * @param onDiskSizeWithHeaderL the on-disk size of the block, including
1581      *          the header, or -1 if unknown; i.e. when iterating over blocks reading
1582      *          in the file metadata info.
1583      * @param pread whether to use a positional read
1584      */
1585     @Override
1586     public HFileBlock readBlockData(long offset, long onDiskSizeWithHeaderL, boolean pread,
1587                                     boolean updateMetrics) throws IOException {
1588       // Get a copy of the current state of whether to validate
1589       // hbase checksums or not for this read call. This is not
1590       // thread-safe but the one constaint is that if we decide
1591       // to skip hbase checksum verification then we are
1592       // guaranteed to use hdfs checksum verification.
1593       boolean doVerificationThruHBaseChecksum = streamWrapper.shouldUseHBaseChecksum();
1594       FSDataInputStream is = streamWrapper.getStream(doVerificationThruHBaseChecksum);
1595 
1596       HFileBlock blk = readBlockDataInternal(is, offset,
1597                          onDiskSizeWithHeaderL, pread,
1598                          doVerificationThruHBaseChecksum, updateMetrics);
1599       if (blk == null) {
1600         HFile.LOG.warn("HBase checksum verification failed for file " +
1601                        pathName + " at offset " +
1602                        offset + " filesize " + fileSize +
1603                        ". Retrying read with HDFS checksums turned on...");
1604 
1605         if (!doVerificationThruHBaseChecksum) {
1606           String msg = "HBase checksum verification failed for file " +
1607                        pathName + " at offset " +
1608                        offset + " filesize " + fileSize +
1609                        " but this cannot happen because doVerify is " +
1610                        doVerificationThruHBaseChecksum;
1611           HFile.LOG.warn(msg);
1612           throw new IOException(msg); // cannot happen case here
1613         }
1614         HFile.CHECKSUM_FAILURES.increment(); // update metrics
1615 
1616         // If we have a checksum failure, we fall back into a mode where
1617         // the next few reads use HDFS level checksums. We aim to make the
1618         // next CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD reads avoid
1619         // hbase checksum verification, but since this value is set without
1620         // holding any locks, it can so happen that we might actually do
1621         // a few more than precisely this number.
1622         is = this.streamWrapper.fallbackToFsChecksum(CHECKSUM_VERIFICATION_NUM_IO_THRESHOLD);
1623         doVerificationThruHBaseChecksum = false;
1624         blk = readBlockDataInternal(is, offset, onDiskSizeWithHeaderL, pread,
1625                                     doVerificationThruHBaseChecksum, updateMetrics);
1626         if (blk != null) {
1627           HFile.LOG.warn("HDFS checksum verification succeeded for file " +
1628                          pathName + " at offset " +
1629                          offset + " filesize " + fileSize);
1630         }
1631       }
1632       if (blk == null && !doVerificationThruHBaseChecksum) {
1633         String msg = "readBlockData failed, possibly due to " +
1634                      "checksum verification failed for file " + pathName +
1635                      " at offset " + offset + " filesize " + fileSize;
1636         HFile.LOG.warn(msg);
1637         throw new IOException(msg);
1638       }
1639 
1640       // If there is a checksum mismatch earlier, then retry with
1641       // HBase checksums switched off and use HDFS checksum verification.
1642       // This triggers HDFS to detect and fix corrupt replicas. The
1643       // next checksumOffCount read requests will use HDFS checksums.
1644       // The decrementing of this.checksumOffCount is not thread-safe,
1645       // but it is harmless because eventually checksumOffCount will be
1646       // a negative number.
1647       streamWrapper.checksumOk();
1648       return blk;
1649     }
1650 
1651      /**
1652      * @return Check <code>onDiskSizeWithHeaderL</code> size is healthy and then return it as an int
1653      * @throws IOException
1654      */
1655     private static int checkAndGetSizeAsInt(final long onDiskSizeWithHeaderL, final int hdrSize)
1656     throws IOException {
1657       if ((onDiskSizeWithHeaderL < hdrSize && onDiskSizeWithHeaderL != -1)
1658           || onDiskSizeWithHeaderL >= Integer.MAX_VALUE) {
1659         throw new IOException("Invalid onDisksize=" + onDiskSizeWithHeaderL
1660             + ": expected to be at least " + hdrSize
1661             + " and at most " + Integer.MAX_VALUE + ", or -1");
1662       }
1663       return (int)onDiskSizeWithHeaderL;
1664     }
1665 
1666     /**
1667      * Check atomic reference cache for this block's header. Cache only good if next
1668      * read coming through is next in sequence in the block. We read next block's
1669      * header on the tail of reading the previous block to save a seek. Otherwise,
1670      * we have to do a seek to read the header before we can pull in the block OR
1671      * we have to backup the stream because we over-read (the next block's header).
1672      * @see PrefetchedHeader
1673      * @return The cached block header or null if not found.
1674      * @see #cacheNextBlockHeader(long, byte[], int, int)
1675      */
1676     private ByteBuffer getCachedHeader(final long offset) {
1677       PrefetchedHeader ph = this.prefetchedHeader.get();
1678       return ph != null && ph.offset == offset? ph.buf: null;
1679     }
1680 
1681     /**
1682      * Save away the next blocks header in atomic reference.
1683      * @see #getCachedHeader(long)
1684      * @see PrefetchedHeader
1685      */
1686     private void cacheNextBlockHeader(final long offset,
1687         final byte [] header, final int headerOffset, final int headerLength) {
1688       PrefetchedHeader ph = new PrefetchedHeader();
1689       ph.offset = offset;
1690       System.arraycopy(header, headerOffset, ph.header, 0, headerLength);
1691       this.prefetchedHeader.set(ph);
1692     }
1693 
1694     /**
1695      * Verify the passed in onDiskSizeWithHeader aligns with what is in the header else something
1696      * is not right.
1697      * @throws IOException
1698      */
1699     private void verifyOnDiskSizeMatchesHeader(final int passedIn, final ByteBuffer headerBuf,
1700         final long offset, boolean verifyChecksum)
1701     throws IOException {
1702       // Assert size provided aligns with what is in the header
1703       int fromHeader = getOnDiskSizeWithHeader(headerBuf, verifyChecksum);
1704       if (passedIn != fromHeader) {
1705         throw new IOException("Passed in onDiskSizeWithHeader=" + passedIn + " != " + fromHeader +
1706             ", offset=" + offset + ", fileContext=" + this.fileContext);
1707       }
1708     }
1709 
1710     /**
1711      * Reads a version 2 block.
1712      *
1713      * @param offset the offset in the stream to read at. Usually the
1714      * @param onDiskSizeWithHeaderL the on-disk size of the block, including
1715      *          the header and checksums if present or -1 if unknown (as a long). Can be -1
1716      *          if we are doing raw iteration of blocks as when loading up file metadata; i.e.
1717      *          the first read of a new file (TODO: Fix! See HBASE-17072). Usually non-null gotten
1718      *          from the file index.
1719      * @param pread whether to use a positional read
1720      * @param verifyChecksum Whether to use HBase checksums.
1721      *        If HBase checksum is switched off, then use HDFS checksum.
1722      * @return the HFileBlock or null if there is a HBase checksum mismatch
1723      */
1724     protected HFileBlock readBlockDataInternal(FSDataInputStream is, long offset,
1725         long onDiskSizeWithHeaderL, boolean pread, boolean verifyChecksum, boolean updateMetrics)
1726     throws IOException {
1727       if (offset < 0) {
1728         throw new IOException("Invalid offset=" + offset + " trying to read "
1729             + "block (onDiskSize=" + onDiskSizeWithHeaderL + ")");
1730       }
1731       int onDiskSizeWithHeader = checkAndGetSizeAsInt(onDiskSizeWithHeaderL, hdrSize);
1732       // Try and get cached header. Will serve us in rare case where onDiskSizeWithHeaderL is -1
1733       // and will save us having to seek the stream backwards to reread the header we
1734       // read the last time through here.
1735       ByteBuffer headerBuf = getCachedHeader(offset);
1736       if (LOG.isTraceEnabled()) {
1737         LOG.trace("Reading " + this.fileContext.getHFileName() + " at offset=" + offset +
1738           ", pread=" + pread + ", verifyChecksum=" + verifyChecksum + ", cachedHeader=" +
1739           headerBuf + ", onDiskSizeWithHeader=" + onDiskSizeWithHeader);
1740       }
1741       long startTime = System.currentTimeMillis();
1742       if (onDiskSizeWithHeader <= 0) {
1743         // We were not passed the block size. Need to get it from the header. If header was not in
1744         // cache, need to seek to pull it in. This is costly and should happen very rarely.
1745         // Currently happens on open of a hfile reader where we read the trailer blocks for
1746         // indices. Otherwise, we are reading block sizes out of the hfile index. To check,
1747         // enable TRACE in this file and you'll get an exception in a LOG every time we seek.
1748         // See HBASE-17072 for more detail.
1749         if (headerBuf == null) {
1750           if (LOG.isTraceEnabled()) {
1751             LOG.trace("Extra see to get block size!", new RuntimeException());
1752           }
1753           headerBuf = ByteBuffer.allocate(hdrSize);
1754           readAtOffset(is, headerBuf.array(), headerBuf.arrayOffset(), hdrSize, false,
1755               offset, pread);
1756         }
1757         onDiskSizeWithHeader = getOnDiskSizeWithHeader(headerBuf,
1758           this.fileContext.isUseHBaseChecksum());
1759       }
1760 
1761       int preReadHeaderSize = headerBuf == null? 0 : hdrSize;
1762       // Allocate enough space to fit the next block's header too; saves a seek next time through.
1763       // onDiskBlock is whole block + header + checksums then extra hdrSize to read next header;
1764       // onDiskSizeWithHeader is header, body, and any checksums if present. preReadHeaderSize
1765       // says where to start reading. If we have the header cached, then we don't need to read
1766       // it again and we can likely read from last place we left off w/o need to backup and reread
1767       // the header we read last time through here. TODO: Review this overread of the header. Is it necessary
1768       // when we get the block size from the hfile index? See note on PrefetchedHeader class above.
1769       byte [] onDiskBlock = new byte[onDiskSizeWithHeader + hdrSize];
1770       int nextBlockOnDiskSize = readAtOffset(is, onDiskBlock, preReadHeaderSize,
1771           onDiskSizeWithHeader - preReadHeaderSize, true, offset + preReadHeaderSize, pread);
1772       if (headerBuf != null) {
1773         // The header has been read when reading the previous block OR in a distinct header-only
1774         // read. Copy to this block's header.
1775         System.arraycopy(headerBuf.array(), headerBuf.arrayOffset(), onDiskBlock, 0, hdrSize);
1776       } else {
1777         headerBuf = ByteBuffer.wrap(onDiskBlock, 0, hdrSize);
1778       }
1779 
1780       // Do a few checks before we go instantiate HFileBlock.
1781       assert onDiskSizeWithHeader > this.hdrSize;
1782       verifyOnDiskSizeMatchesHeader(onDiskSizeWithHeader, headerBuf, offset,
1783         this.fileContext.isUseHBaseChecksum());
1784       ByteBuffer onDiskBlockByteBuffer = ByteBuffer.wrap(onDiskBlock, 0, onDiskSizeWithHeader);
1785       // Verify checksum of the data before using it for building HFileBlock.
1786       if (verifyChecksum &&
1787           !validateChecksum(offset, onDiskBlockByteBuffer, hdrSize)) {
1788         return null;
1789       }
1790       long duration = System.currentTimeMillis() - startTime;
1791       if (updateMetrics) {
1792         HFile.updateReadLatency(duration, pread);
1793       }
1794       // The onDiskBlock will become the headerAndDataBuffer for this block.
1795       // If nextBlockOnDiskSizeWithHeader is not zero, the onDiskBlock already
1796       // contains the header of next block, so no need to set next block's header in it.
1797       HFileBlock hFileBlock =
1798           new HFileBlock(onDiskBlockByteBuffer, this.fileContext.isUseHBaseChecksum(), offset,
1799               nextBlockOnDiskSize, fileContext);
1800       // Run check on uncompressed sizings.
1801       if (!fileContext.isCompressedOrEncrypted()) {
1802         hFileBlock.sanityCheckUncompressed();
1803       }
1804       if (LOG.isTraceEnabled()) {
1805         LOG.trace("Read " + hFileBlock + " in " + duration + " ns");
1806       }
1807       // Cache next block header if we read it for the next time through here.
1808       if (nextBlockOnDiskSize != -1) {
1809         cacheNextBlockHeader(offset + hFileBlock.getOnDiskSizeWithHeader(),
1810             onDiskBlock, onDiskSizeWithHeader, hdrSize);
1811       }
1812       return hFileBlock;
1813     }
1814 
1815     void setIncludesMemstoreTS(boolean includesMemstoreTS) {
1816       this.fileContext.setIncludesMvcc(includesMemstoreTS);
1817     }
1818 
1819     void setDataBlockEncoder(HFileDataBlockEncoder encoder) {
1820       encodedBlockDecodingCtx = encoder.newDataBlockDecodingContext(this.fileContext);
1821     }
1822 
1823     @Override
1824     public HFileBlockDecodingContext getBlockDecodingContext() {
1825       return this.encodedBlockDecodingCtx;
1826     }
1827 
1828     @Override
1829     public HFileBlockDecodingContext getDefaultBlockDecodingContext() {
1830       return this.defaultDecodingCtx;
1831     }
1832 
1833     /**
1834      * Generates the checksum for the header as well as the data and then validates it.
1835      * If the block doesn't uses checksum, returns false.
1836      * @return True if checksum matches, else false.
1837      */
1838     protected boolean validateChecksum(long offset, ByteBuffer data, int hdrSize)
1839         throws IOException {
1840       // If this is an older version of the block that does not have checksums, then return false
1841       // indicating that checksum verification did not succeed. Actually, this method should never
1842       // be called when the minorVersion is 0, thus this is a defensive check for a cannot-happen
1843       // case. Since this is a cannot-happen case, it is better to return false to indicate a
1844       // checksum validation failure.
1845       if (!fileContext.isUseHBaseChecksum()) {
1846         return false;
1847       }
1848       return ChecksumUtil.validateChecksum(data, pathName, offset, hdrSize);
1849     }
1850 
1851     @Override
1852     public void closeStreams() throws IOException {
1853       streamWrapper.close();
1854     }
1855 
1856     @Override
1857     public void unbufferStream() {
1858       // To handle concurrent reads, ensure that no other client is accessing the streams while we
1859       // unbuffer it.
1860       if (streamLock.tryLock()) {
1861         try {
1862           this.streamWrapper.unbuffer();
1863         } finally {
1864           streamLock.unlock();
1865         }
1866       }
1867     }
1868 
1869     @Override
1870     public String toString() {
1871       return "hfs=" + hfs + ", path=" + pathName + ", fileContext=" + fileContext;
1872     }
1873   }
1874 
1875   /** An additional sanity-check in case no compression or encryption is being used. */
1876   void sanityCheckUncompressed() throws IOException {
1877     if (onDiskSizeWithoutHeader != uncompressedSizeWithoutHeader +
1878         totalChecksumBytes()) {
1879       throw new IOException("Using no compression but "
1880           + "onDiskSizeWithoutHeader=" + onDiskSizeWithoutHeader + ", "
1881           + "uncompressedSizeWithoutHeader=" + uncompressedSizeWithoutHeader
1882           + ", numChecksumbytes=" + totalChecksumBytes());
1883     }
1884   }
1885 
1886   // Cacheable implementation
1887   @Override
1888   public int getSerializedLength() {
1889     if (buf != null) {
1890       // Include extra bytes for block metadata.
1891       return this.buf.limit() + BLOCK_METADATA_SPACE;
1892     }
1893     return 0;
1894   }
1895 
1896   // Cacheable implementation
1897   @Override
1898   public void serialize(ByteBuffer destination, boolean includeNextBlockOnDiskSize) {
1899     ByteBufferUtils.copyFromBufferToBuffer(destination, this.buf, 0,
1900         getSerializedLength() - BLOCK_METADATA_SPACE);
1901     destination = addMetaData(destination, includeNextBlockOnDiskSize);
1902 
1903     // Make it ready for reading. flip sets position to zero and limit to current position which
1904     // is what we want if we do not want to serialize the block plus checksums if present plus
1905     // metadata.
1906     destination.flip();
1907   }
1908 
1909   /**
1910    * For use by bucketcache. This exposes internals.
1911    */
1912   public ByteBuffer getMetaData() {
1913     ByteBuffer bb = ByteBuffer.allocate(BLOCK_METADATA_SPACE);
1914     bb = addMetaData(bb, true);
1915     bb.flip();
1916     return bb;
1917   }
1918 
1919   /**
1920    * Adds metadata at current position (position is moved forward). Does not flip or reset.
1921    * @return The passed <code>destination</code> with metadata added.
1922    */
1923   private ByteBuffer addMetaData(final ByteBuffer destination, boolean includeNextBlockMetadata) {
1924     destination.put(this.fileContext.isUseHBaseChecksum() ? (byte) 1 : (byte) 0);
1925     destination.putLong(this.offset);
1926     if (includeNextBlockMetadata) {
1927       destination.putInt(this.nextBlockOnDiskSize);
1928     }
1929     return destination;
1930   }
1931 
1932   // Cacheable implementation
1933   @Override
1934   public CacheableDeserializer<Cacheable> getDeserializer() {
1935     return HFileBlock.BLOCK_DESERIALIZER;
1936   }
1937 
1938   @Override
1939   public int hashCode() {
1940     final int prime = 31;
1941     int result = 1;
1942     result = prime * result + ((blockType == null) ? 0 : blockType.hashCode());
1943     result = prime * result + ((buf == null) ? 0 : buf.hashCode());
1944     result = prime * result + ((fileContext == null) ? 0 : fileContext.hashCode());
1945     result = prime * result + nextBlockOnDiskSize;
1946     result = prime * result + (int) (offset ^ (offset >>> 32));
1947     result = prime * result + onDiskDataSizeWithHeader;
1948     result = prime * result + onDiskSizeWithoutHeader;
1949     result = prime * result + (int) (prevBlockOffset ^ (prevBlockOffset >>> 32));
1950     result = prime * result + uncompressedSizeWithoutHeader;
1951     return result;
1952   }
1953 
1954   @Override
1955   public boolean equals(Object comparison) {
1956     if (this == comparison) {
1957       return true;
1958     }
1959     if (comparison == null) {
1960       return false;
1961     }
1962     if (comparison.getClass() != this.getClass()) {
1963       return false;
1964     }
1965 
1966     HFileBlock castedComparison = (HFileBlock) comparison;
1967 
1968     if (castedComparison.blockType != this.blockType) {
1969       return false;
1970     }
1971     if (castedComparison.nextBlockOnDiskSize != this.nextBlockOnDiskSize) {
1972       return false;
1973     }
1974     // Offset is important. Needed when we have to remake cachekey when block is returned to cache.
1975     if (castedComparison.offset != this.offset) {
1976       return false;
1977     }
1978     if (castedComparison.onDiskSizeWithoutHeader != this.onDiskSizeWithoutHeader) {
1979       return false;
1980     }
1981     if (castedComparison.prevBlockOffset != this.prevBlockOffset) {
1982       return false;
1983     }
1984     if (castedComparison.uncompressedSizeWithoutHeader != this.uncompressedSizeWithoutHeader) {
1985       return false;
1986     }
1987     if (ByteBufferUtils.compareTo(this.buf, 0, this.buf.limit(), castedComparison.buf, 0,
1988         castedComparison.buf.limit()) != 0) {
1989       return false;
1990     }
1991     return true;
1992   }
1993 
1994   public DataBlockEncoding getDataBlockEncoding() {
1995     if (blockType == BlockType.ENCODED_DATA) {
1996       return DataBlockEncoding.getEncodingById(getDataBlockEncodingId());
1997     }
1998     return DataBlockEncoding.NONE;
1999   }
2000 
2001   byte getChecksumType() {
2002     return this.fileContext.getChecksumType().getCode();
2003   }
2004 
2005   int getBytesPerChecksum() {
2006     return this.fileContext.getBytesPerChecksum();
2007   }
2008 
2009   /** @return the size of data on disk + header. Excludes checksum. */
2010   int getOnDiskDataSizeWithHeader() {
2011     return this.onDiskDataSizeWithHeader;
2012   }
2013 
2014   /**
2015    * Calculate the number of bytes required to store all the checksums
2016    * for this block. Each checksum value is a 4 byte integer.
2017    */
2018   int totalChecksumBytes() {
2019     // If the hfile block has minorVersion 0, then there are no checksum
2020     // data to validate. Similarly, a zero value in this.bytesPerChecksum
2021     // indicates that cached blocks do not have checksum data because
2022     // checksums were already validated when the block was read from disk.
2023     if (!fileContext.isUseHBaseChecksum() || this.fileContext.getBytesPerChecksum() == 0) {
2024       return 0;
2025     }
2026     return (int) ChecksumUtil.numBytes(onDiskDataSizeWithHeader,
2027         this.fileContext.getBytesPerChecksum());
2028   }
2029 
2030   /**
2031    * Returns the size of this block header.
2032    */
2033   public int headerSize() {
2034     return headerSize(this.fileContext.isUseHBaseChecksum());
2035   }
2036 
2037   /**
2038    * Maps a minor version to the size of the header.
2039    */
2040   public static int headerSize(boolean usesHBaseChecksum) {
2041     return usesHBaseChecksum?
2042         HConstants.HFILEBLOCK_HEADER_SIZE: HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM;
2043   }
2044 
2045   /**
2046    * Return the appropriate DUMMY_HEADER for the minor version
2047    */
2048   byte[] getDummyHeaderForVersion() {
2049     return getDummyHeaderForVersion(this.fileContext.isUseHBaseChecksum());
2050   }
2051 
2052   /**
2053    * Return the appropriate DUMMY_HEADER for the minor version
2054    */
2055   static private byte[] getDummyHeaderForVersion(boolean usesHBaseChecksum) {
2056     return usesHBaseChecksum? HConstants.HFILEBLOCK_DUMMY_HEADER: DUMMY_HEADER_NO_CHECKSUM;
2057   }
2058 
2059   /**
2060    * @return This HFileBlocks fileContext which will a derivative of the
2061    * fileContext for the file from which this block's data was originally read.
2062    */
2063   HFileContext getHFileContext() {
2064     return this.fileContext;
2065   }
2066 
2067   /**
2068    * Convert the contents of the block header into a human readable string.
2069    * This is mostly helpful for debugging. This assumes that the block
2070    * has minor version > 0.
2071    */
2072   @VisibleForTesting
2073   static String toStringHeader(ByteBuffer buf) throws IOException {
2074     byte[] magicBuf = new byte[Math.min(buf.limit() - buf.position(), BlockType.MAGIC_LENGTH)];
2075     buf.get(magicBuf);
2076     BlockType bt = BlockType.parse(magicBuf, 0, BlockType.MAGIC_LENGTH);
2077     int compressedBlockSizeNoHeader = buf.getInt();
2078     int uncompressedBlockSizeNoHeader = buf.getInt();
2079     long prevBlockOffset = buf.getLong();
2080     byte cksumtype = buf.get();
2081     long bytesPerChecksum = buf.getInt();
2082     long onDiskDataSizeWithHeader = buf.getInt();
2083     return " Header dump: magic: " + Bytes.toString(magicBuf) +
2084                    " blockType " + bt +
2085                    " compressedBlockSizeNoHeader " +
2086                    compressedBlockSizeNoHeader +
2087                    " uncompressedBlockSizeNoHeader " +
2088                    uncompressedBlockSizeNoHeader +
2089                    " prevBlockOffset " + prevBlockOffset +
2090                    " checksumType " + ChecksumType.codeToType(cksumtype) +
2091                    " bytesPerChecksum " + bytesPerChecksum +
2092                    " onDiskDataSizeWithHeader " + onDiskDataSizeWithHeader;
2093   }
2094 }