BlockBasedFile.java

  1. /*
  2.  * Copyright (C) 2017, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.storage.dfs;

  11. import java.io.EOFException;
  12. import java.io.IOException;
  13. import java.nio.ByteBuffer;
  14. import java.text.MessageFormat;

  15. import org.eclipse.jgit.errors.PackInvalidException;
  16. import org.eclipse.jgit.internal.storage.pack.PackExt;

  17. /** Block based file stored in {@link DfsBlockCache}. */
  18. abstract class BlockBasedFile {
  19.     /** Cache that owns this file and its data. */
  20.     final DfsBlockCache cache;

  21.     /** Unique identity of this file while in-memory. */
  22.     final DfsStreamKey key;

  23.     /** Description of the associated pack file's storage. */
  24.     final DfsPackDescription desc;
  25.     final PackExt ext;

  26.     /**
  27.      * Preferred alignment for loading blocks from the backing file.
  28.      * <p>
  29.      * It is initialized to 0 and filled in on the first read made from the
  30.      * file. Block sizes may be odd, e.g. 4091, caused by the underling DFS
  31.      * storing 4091 user bytes and 5 bytes block metadata into a lower level
  32.      * 4096 byte block on disk.
  33.      */
  34.     volatile int blockSize;

  35.     /**
  36.      * Total number of bytes in this pack file.
  37.      * <p>
  38.      * This field initializes to -1 and gets populated when a block is loaded.
  39.      */
  40.     volatile long length;

  41.     /** True once corruption has been detected that cannot be worked around. */
  42.     volatile boolean invalid;

  43.     /** Exception that caused the packfile to be flagged as invalid */
  44.     protected volatile Exception invalidatingCause;

  45.     BlockBasedFile(DfsBlockCache cache, DfsPackDescription desc, PackExt ext) {
  46.         this.cache = cache;
  47.         this.key = desc.getStreamKey(ext);
  48.         this.desc = desc;
  49.         this.ext = ext;
  50.     }

  51.     String getFileName() {
  52.         return desc.getFileName(ext);
  53.     }

  54.     boolean invalid() {
  55.         return invalid;
  56.     }

  57.     void setInvalid() {
  58.         invalid = true;
  59.     }

  60.     void setBlockSize(int newSize) {
  61.         blockSize = newSize;
  62.     }

  63.     long alignToBlock(long pos) {
  64.         int size = blockSize;
  65.         if (size == 0)
  66.             size = cache.getBlockSize();
  67.         return (pos / size) * size;
  68.     }

  69.     int blockSize(ReadableChannel rc) {
  70.         // If the block alignment is not yet known, discover it. Prefer the
  71.         // larger size from either the cache or the file itself.
  72.         int size = blockSize;
  73.         if (size == 0) {
  74.             size = rc.blockSize();
  75.             if (size <= 0)
  76.                 size = cache.getBlockSize();
  77.             else if (size < cache.getBlockSize())
  78.                 size = (cache.getBlockSize() / size) * size;
  79.             blockSize = size;
  80.         }
  81.         return size;
  82.     }

  83.     DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
  84.         try (LazyChannel c = new LazyChannel(ctx, desc, ext)) {
  85.             return cache.getOrLoad(this, pos, ctx, c);
  86.         }
  87.     }

  88.     DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc)
  89.             throws IOException {
  90.         if (invalid) {
  91.             throw new PackInvalidException(getFileName(), invalidatingCause);
  92.         }

  93.         ctx.stats.readBlock++;
  94.         long start = System.nanoTime();
  95.         try {
  96.             int size = blockSize(rc);
  97.             pos = (pos / size) * size;

  98.             // If the size of the file is not yet known, try to discover it.
  99.             // Channels may choose to return -1 to indicate they don't
  100.             // know the length yet, in this case read up to the size unit
  101.             // given by the caller, then recheck the length.
  102.             long len = length;
  103.             if (len < 0) {
  104.                 len = rc.size();
  105.                 if (0 <= len)
  106.                     length = len;
  107.             }

  108.             if (0 <= len && len < pos + size)
  109.                 size = (int) (len - pos);
  110.             if (size <= 0)
  111.                 throw new EOFException(MessageFormat.format(
  112.                         DfsText.get().shortReadOfBlock, Long.valueOf(pos),
  113.                         getFileName(), Long.valueOf(0), Long.valueOf(0)));

  114.             byte[] buf = new byte[size];
  115.             rc.position(pos);
  116.             int cnt = read(rc, ByteBuffer.wrap(buf, 0, size));
  117.             ctx.stats.readBlockBytes += cnt;
  118.             if (cnt != size) {
  119.                 if (0 <= len) {
  120.                     throw new EOFException(MessageFormat.format(
  121.                             DfsText.get().shortReadOfBlock, Long.valueOf(pos),
  122.                             getFileName(), Integer.valueOf(size),
  123.                             Integer.valueOf(cnt)));
  124.                 }

  125.                 // Assume the entire thing was read in a single shot, compact
  126.                 // the buffer to only the space required.
  127.                 byte[] n = new byte[cnt];
  128.                 System.arraycopy(buf, 0, n, 0, n.length);
  129.                 buf = n;
  130.             } else if (len < 0) {
  131.                 // With no length at the start of the read, the channel should
  132.                 // have the length available at the end.
  133.                 length = len = rc.size();
  134.             }

  135.             return new DfsBlock(key, pos, buf);
  136.         } finally {
  137.             ctx.stats.readBlockMicros += elapsedMicros(start);
  138.         }
  139.     }

  140.     static int read(ReadableChannel rc, ByteBuffer buf) throws IOException {
  141.         int n;
  142.         do {
  143.             n = rc.read(buf);
  144.         } while (0 < n && buf.hasRemaining());
  145.         return buf.position();
  146.     }

  147.     static long elapsedMicros(long start) {
  148.         return (System.nanoTime() - start) / 1000L;
  149.     }

  150.     /**
  151.      * A supplier of readable channel that opens the channel lazily.
  152.      */
  153.     private static class LazyChannel
  154.             implements AutoCloseable, DfsBlockCache.ReadableChannelSupplier {
  155.         private final DfsReader ctx;
  156.         private final DfsPackDescription desc;
  157.         private final PackExt ext;

  158.         private ReadableChannel rc;

  159.         LazyChannel(DfsReader ctx, DfsPackDescription desc, PackExt ext) {
  160.             this.ctx = ctx;
  161.             this.desc = desc;
  162.             this.ext = ext;
  163.         }

  164.         @Override
  165.         public ReadableChannel get() throws IOException {
  166.             if (rc == null) {
  167.                 rc = ctx.db.openFile(desc, ext);
  168.             }
  169.             return rc;
  170.         }

  171.         @Override
  172.         public void close() throws IOException {
  173.             if (rc != null) {
  174.                 rc.close();
  175.             }
  176.         }
  177.     }
  178. }