DeltaWindow.java

  1. /*
  2.  * Copyright (C) 2010, 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.pack;

  11. import java.io.EOFException;
  12. import java.io.IOException;
  13. import java.io.OutputStream;
  14. import java.util.zip.Deflater;

  15. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  16. import org.eclipse.jgit.errors.LargeObjectException;
  17. import org.eclipse.jgit.errors.MissingObjectException;
  18. import org.eclipse.jgit.lib.ObjectReader;
  19. import org.eclipse.jgit.lib.ProgressMonitor;
  20. import org.eclipse.jgit.storage.pack.PackConfig;
  21. import org.eclipse.jgit.util.TemporaryBuffer;

  22. final class DeltaWindow {
  23.     private static final boolean NEXT_RES = false;
  24.     private static final boolean NEXT_SRC = true;

  25.     private final PackConfig config;
  26.     private final DeltaCache deltaCache;
  27.     private final ObjectReader reader;
  28.     private final ProgressMonitor monitor;
  29.     private final long bytesPerUnit;
  30.     private long bytesProcessed;

  31.     /** Maximum number of bytes to admit to the window at once. */
  32.     private final long maxMemory;

  33.     /** Maximum depth we should create for any delta chain. */
  34.     private final int maxDepth;

  35.     private final ObjectToPack[] toSearch;
  36.     private int cur;
  37.     private int end;

  38.     /** Amount of memory we have loaded right now. */
  39.     private long loaded;

  40.     // The object we are currently considering needs a lot of state:

  41.     /** Window entry of the object we are currently considering. */
  42.     private DeltaWindowEntry res;

  43.     /** If we have chosen a base, the window entry it was created from. */
  44.     private DeltaWindowEntry bestBase;
  45.     private int deltaLen;
  46.     private Object deltaBuf;

  47.     /** Used to compress cached deltas. */
  48.     private Deflater deflater;

  49.     DeltaWindow(PackConfig pc, DeltaCache dc, ObjectReader or,
  50.             ProgressMonitor pm, long bpu,
  51.             ObjectToPack[] in, int beginIndex, int endIndex) {
  52.         config = pc;
  53.         deltaCache = dc;
  54.         reader = or;
  55.         monitor = pm;
  56.         bytesPerUnit = bpu;
  57.         toSearch = in;
  58.         cur = beginIndex;
  59.         end = endIndex;

  60.         maxMemory = Math.max(0, config.getDeltaSearchMemoryLimit());
  61.         maxDepth = config.getMaxDeltaDepth();
  62.         res = DeltaWindowEntry.createWindow(config.getDeltaSearchWindowSize());
  63.     }

  64.     synchronized DeltaTask.Slice remaining() {
  65.         int e = end;
  66.         int halfRemaining = (e - cur) >>> 1;
  67.         if (0 == halfRemaining)
  68.             return null;

  69.         int split = e - halfRemaining;
  70.         int h = toSearch[split].getPathHash();

  71.         // Attempt to split on the next path after the 50% split point.
  72.         for (int n = split + 1; n < e; n++) {
  73.             if (h != toSearch[n].getPathHash())
  74.                 return new DeltaTask.Slice(n, e);
  75.         }

  76.         if (h != toSearch[cur].getPathHash()) {
  77.             // Try to split on the path before the 50% split point.
  78.             // Do not split the path currently being processed.
  79.             for (int p = split - 1; cur < p; p--) {
  80.                 if (h != toSearch[p].getPathHash())
  81.                     return new DeltaTask.Slice(p + 1, e);
  82.             }
  83.         }
  84.         return null;
  85.     }

  86.     synchronized boolean tryStealWork(DeltaTask.Slice s) {
  87.         if (s.beginIndex <= cur || end <= s.beginIndex)
  88.             return false;
  89.         end = s.beginIndex;
  90.         return true;
  91.     }

  92.     void search() throws IOException {
  93.         try {
  94.             for (;;) {
  95.                 ObjectToPack next;
  96.                 synchronized (this) {
  97.                     if (end <= cur)
  98.                         break;
  99.                     next = toSearch[cur++];
  100.                 }
  101.                 if (maxMemory != 0) {
  102.                     clear(res);
  103.                     final long need = estimateSize(next);
  104.                     DeltaWindowEntry n = res.next;
  105.                     for (; maxMemory < loaded + need && n != res; n = n.next)
  106.                         clear(n);
  107.                 }
  108.                 res.set(next);
  109.                 clearWindowOnTypeSwitch();

  110.                 if (res.object.isEdge() || res.object.doNotAttemptDelta()) {
  111.                     // We don't actually want to make a delta for
  112.                     // them, just need to push them into the window
  113.                     // so they can be read by other objects.
  114.                     keepInWindow();
  115.                 } else {
  116.                     // Search for a delta for the current window slot.
  117.                     if (bytesPerUnit <= (bytesProcessed += next.getWeight())) {
  118.                         int d = (int) (bytesProcessed / bytesPerUnit);
  119.                         monitor.update(d);
  120.                         bytesProcessed -= d * bytesPerUnit;
  121.                     }
  122.                     searchInWindow();
  123.                 }
  124.             }
  125.         } finally {
  126.             if (deflater != null)
  127.                 deflater.end();
  128.         }
  129.     }

  130.     private static long estimateSize(ObjectToPack ent) {
  131.         return DeltaIndex.estimateIndexSize(ent.getWeight());
  132.     }

  133.     private static long estimateIndexSize(DeltaWindowEntry ent) {
  134.         if (ent.buffer == null)
  135.             return estimateSize(ent.object);

  136.         int len = ent.buffer.length;
  137.         return DeltaIndex.estimateIndexSize(len) - len;
  138.     }

  139.     private void clearWindowOnTypeSwitch() {
  140.         DeltaWindowEntry p = res.prev;
  141.         if (!p.empty() && res.type() != p.type()) {
  142.             for (; p != res; p = p.prev) {
  143.                 clear(p);
  144.             }
  145.         }
  146.     }

  147.     private void clear(DeltaWindowEntry ent) {
  148.         if (ent.index != null)
  149.             loaded -= ent.index.getIndexSize();
  150.         else if (ent.buffer != null)
  151.             loaded -= ent.buffer.length;
  152.         ent.set(null);
  153.     }

  154.     private void searchInWindow() throws IOException {
  155.         // Loop through the window backwards, considering every entry.
  156.         // This lets us look at the bigger objects that came before.
  157.         for (DeltaWindowEntry src = res.prev; src != res; src = src.prev) {
  158.             if (src.empty())
  159.                 break;
  160.             if (delta(src) /* == NEXT_SRC */)
  161.                 continue;
  162.             bestBase = null;
  163.             deltaBuf = null;
  164.             return;
  165.         }

  166.         // We couldn't find a suitable delta for this object, but it may
  167.         // still be able to act as a base for another one.
  168.         if (bestBase == null) {
  169.             keepInWindow();
  170.             return;
  171.         }

  172.         // Select this best matching delta as the base for the object.
  173.         //
  174.         ObjectToPack srcObj = bestBase.object;
  175.         ObjectToPack resObj = res.object;
  176.         if (srcObj.isEdge()) {
  177.             // The source (the delta base) is an edge object outside of the
  178.             // pack. Its part of the common base set that the peer already
  179.             // has on hand, so we don't want to send it. We have to store
  180.             // an ObjectId and *NOT* an ObjectToPack for the base to ensure
  181.             // the base isn't included in the outgoing pack file.
  182.             resObj.setDeltaBase(srcObj.copy());
  183.         } else {
  184.             // The base is part of the pack we are sending, so it should be
  185.             // a direct pointer to the base.
  186.             resObj.setDeltaBase(srcObj);
  187.         }

  188.         int depth = srcObj.getDeltaDepth() + 1;
  189.         resObj.setDeltaDepth(depth);
  190.         resObj.clearReuseAsIs();
  191.         cacheDelta(srcObj, resObj);

  192.         if (depth < maxDepth) {
  193.             // Reorder the window so that the best base will be tested
  194.             // first for the next object, and the current object will
  195.             // be the second candidate to consider before any others.
  196.             res.makeNext(bestBase);
  197.             res = bestBase.next;
  198.         }

  199.         bestBase = null;
  200.         deltaBuf = null;
  201.     }

  202.     private boolean delta(DeltaWindowEntry src)
  203.             throws IOException {
  204.         // If the sizes are radically different, this is a bad pairing.
  205.         if (res.size() < src.size() >>> 4)
  206.             return NEXT_SRC;

  207.         int msz = deltaSizeLimit(src);
  208.         if (msz <= 8) // Nearly impossible to fit useful delta.
  209.             return NEXT_SRC;

  210.         // If we have to insert a lot to make this work, find another.
  211.         if (res.size() - src.size() > msz)
  212.             return NEXT_SRC;

  213.         DeltaIndex srcIndex;
  214.         try {
  215.             srcIndex = index(src);
  216.         } catch (LargeObjectException tooBig) {
  217.             // If the source is too big to work on, skip it.
  218.             return NEXT_SRC;
  219.         } catch (IOException notAvailable) {
  220.             if (src.object.isEdge()) // Missing edges are OK.
  221.                 return NEXT_SRC;
  222.             throw notAvailable;
  223.         }

  224.         byte[] resBuf;
  225.         try {
  226.             resBuf = buffer(res);
  227.         } catch (LargeObjectException tooBig) {
  228.             // If its too big, move on to another item.
  229.             return NEXT_RES;
  230.         }

  231.         try {
  232.             OutputStream delta = msz <= (8 << 10)
  233.                 ? new ArrayStream(msz)
  234.                 : new TemporaryBuffer.Heap(msz);
  235.             if (srcIndex.encode(delta, resBuf, msz))
  236.                 selectDeltaBase(src, delta);
  237.         } catch (IOException deltaTooBig) {
  238.             // Unlikely, encoder should see limit and return false.
  239.         }
  240.         return NEXT_SRC;
  241.     }

  242.     private void selectDeltaBase(DeltaWindowEntry src, OutputStream delta) {
  243.         bestBase = src;

  244.         if (delta instanceof ArrayStream) {
  245.             ArrayStream a = (ArrayStream) delta;
  246.             deltaBuf = a.buf;
  247.             deltaLen = a.cnt;
  248.         } else {
  249.             TemporaryBuffer.Heap b = (TemporaryBuffer.Heap) delta;
  250.             deltaBuf = b;
  251.             deltaLen = (int) b.length();
  252.         }
  253.     }

  254.     private int deltaSizeLimit(DeltaWindowEntry src) {
  255.         if (bestBase == null) {
  256.             // Any delta should be no more than 50% of the original size
  257.             // (for text files deflate of whole form should shrink 50%).
  258.             int n = res.size() >>> 1;

  259.             // Evenly distribute delta size limits over allowed depth.
  260.             // If src is non-delta (depth = 0), delta <= 50% of original.
  261.             // If src is almost at limit (9/10), delta <= 10% of original.
  262.             return n * (maxDepth - src.depth()) / maxDepth;
  263.         }

  264.         // With a delta base chosen any new delta must be "better".
  265.         // Retain the distribution described above.
  266.         int d = bestBase.depth();
  267.         int n = deltaLen;

  268.         // If src is whole (depth=0) and base is near limit (depth=9/10)
  269.         // any delta using src can be 10x larger and still be better.
  270.         //
  271.         // If src is near limit (depth=9/10) and base is whole (depth=0)
  272.         // a new delta dependent on src must be 1/10th the size.
  273.         return n * (maxDepth - src.depth()) / (maxDepth - d);
  274.     }

  275.     private void cacheDelta(ObjectToPack srcObj, ObjectToPack resObj) {
  276.         if (deltaCache.canCache(deltaLen, srcObj, resObj)) {
  277.             try {
  278.                 byte[] zbuf = new byte[deflateBound(deltaLen)];
  279.                 ZipStream zs = new ZipStream(deflater(), zbuf);
  280.                 if (deltaBuf instanceof byte[])
  281.                     zs.write((byte[]) deltaBuf, 0, deltaLen);
  282.                 else
  283.                     ((TemporaryBuffer.Heap) deltaBuf).writeTo(zs, null);
  284.                 deltaBuf = null;
  285.                 int len = zs.finish();

  286.                 resObj.setCachedDelta(deltaCache.cache(zbuf, len, deltaLen));
  287.                 resObj.setCachedSize(deltaLen);
  288.             } catch (IOException | OutOfMemoryError err) {
  289.                 deltaCache.credit(deltaLen);
  290.             }
  291.         }
  292.     }

  293.     private static int deflateBound(int insz) {
  294.         return insz + ((insz + 7) >> 3) + ((insz + 63) >> 6) + 11;
  295.     }

  296.     private void keepInWindow() {
  297.         res = res.next;
  298.     }

  299.     private DeltaIndex index(DeltaWindowEntry ent)
  300.             throws MissingObjectException, IncorrectObjectTypeException,
  301.             IOException, LargeObjectException {
  302.         DeltaIndex idx = ent.index;
  303.         if (idx == null) {
  304.             checkLoadable(ent, estimateIndexSize(ent));

  305.             try {
  306.                 idx = new DeltaIndex(buffer(ent));
  307.             } catch (OutOfMemoryError noMemory) {
  308.                 LargeObjectException.OutOfMemory e;
  309.                 e = new LargeObjectException.OutOfMemory(noMemory);
  310.                 e.setObjectId(ent.object);
  311.                 throw e;
  312.             }
  313.             if (maxMemory != 0)
  314.                 loaded += idx.getIndexSize() - idx.getSourceSize();
  315.             ent.index = idx;
  316.         }
  317.         return idx;
  318.     }

  319.     private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException,
  320.             IncorrectObjectTypeException, IOException, LargeObjectException {
  321.         byte[] buf = ent.buffer;
  322.         if (buf == null) {
  323.             checkLoadable(ent, ent.size());

  324.             buf = PackWriter.buffer(config, reader, ent.object);
  325.             if (maxMemory != 0)
  326.                 loaded += buf.length;
  327.             ent.buffer = buf;
  328.         }
  329.         return buf;
  330.     }

  331.     private void checkLoadable(DeltaWindowEntry ent, long need) {
  332.         if (maxMemory == 0)
  333.             return;

  334.         DeltaWindowEntry n = res.next;
  335.         for (; maxMemory < loaded + need; n = n.next) {
  336.             clear(n);
  337.             if (n == ent)
  338.                 throw new LargeObjectException.ExceedsLimit(
  339.                         maxMemory, loaded + need);
  340.         }
  341.     }

  342.     private Deflater deflater() {
  343.         if (deflater == null)
  344.             deflater = new Deflater(config.getCompressionLevel());
  345.         else
  346.             deflater.reset();
  347.         return deflater;
  348.     }

  349.     static final class ZipStream extends OutputStream {
  350.         private final Deflater deflater;

  351.         private final byte[] zbuf;

  352.         private int outPtr;

  353.         ZipStream(Deflater deflater, byte[] zbuf) {
  354.             this.deflater = deflater;
  355.             this.zbuf = zbuf;
  356.         }

  357.         int finish() throws IOException {
  358.             deflater.finish();
  359.             for (;;) {
  360.                 if (outPtr == zbuf.length)
  361.                     throw new EOFException();

  362.                 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
  363.                 if (n == 0) {
  364.                     if (deflater.finished())
  365.                         return outPtr;
  366.                     throw new IOException();
  367.                 }
  368.                 outPtr += n;
  369.             }
  370.         }

  371.         @Override
  372.         public void write(byte[] b, int off, int len) throws IOException {
  373.             deflater.setInput(b, off, len);
  374.             for (;;) {
  375.                 if (outPtr == zbuf.length)
  376.                     throw new EOFException();

  377.                 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
  378.                 if (n == 0) {
  379.                     if (deflater.needsInput())
  380.                         break;
  381.                     throw new IOException();
  382.                 }
  383.                 outPtr += n;
  384.             }
  385.         }

  386.         @Override
  387.         public void write(int b) throws IOException {
  388.             throw new UnsupportedOperationException();
  389.         }
  390.     }

  391.     static final class ArrayStream extends OutputStream {
  392.         final byte[] buf;
  393.         int cnt;

  394.         ArrayStream(int max) {
  395.             buf = new byte[max];
  396.         }

  397.         @Override
  398.         public void write(int b) throws IOException {
  399.             if (cnt == buf.length)
  400.                 throw new IOException();
  401.             buf[cnt++] = (byte) b;
  402.         }

  403.         @Override
  404.         public void write(byte[] b, int off, int len) throws IOException {
  405.             if (len > buf.length - cnt)
  406.                 throw new IOException();
  407.             System.arraycopy(b, off, buf, cnt, len);
  408.             cnt += len;
  409.         }
  410.     }
  411. }