Browse Source

Use heap based stack for PackFile deltas

Instead of using the current thread's stack to recurse through the
delta chain, use a linked list that is stored in the heap.  This
permits the any thread to load a deep delta chain without running out
of thread stack space.

Despite needing to allocate a stack entry object for each delta
visited along the chain being loaded, the object allocation count is
kept the same as in the prior version by removing the transient
ObjectLoaders from the intermediate objects accessed in the chain.
Instead the byte[] for the raw data is passed, and null is used as a
magic value to signal isLarge() and enter the large object code path.

Like the old version, this implementation minimizes the amount of
memory that must be live at once.  The current delta instruction
sequence, the base it applies onto, and the result are the only live
data arrays.  As each level is processed, the prior base is discarded
and replaced with the new result.

Each Delta frame on the stack is slightly larger than the standard
ObjectLoader.SmallObject type that was used before, however the Delta
instances should be smaller than the old method stack frames, so total
memory usage should actually be lower with this new implementation.

Change-Id: I6faca2a440020309658ca23fbec4c95aa637051c
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
stable-0.11
Shawn O. Pearce 14 years ago
parent
commit
165358bc99
  1. 265
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java

265
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackFile.java

@ -63,7 +63,6 @@ import java.util.zip.Inflater;
import org.eclipse.jgit.JGitText; import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.PackInvalidException; import org.eclipse.jgit.errors.PackInvalidException;
import org.eclipse.jgit.errors.PackMismatchException; import org.eclipse.jgit.errors.PackMismatchException;
@ -275,11 +274,25 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
return getReverseIdx().findObject(offset); return getReverseIdx().findObject(offset);
} }
private final void decompress(final long position, final WindowCursor curs, private final byte[] decompress(final long position, final int sz,
final byte[] dstbuf, final int dstoff, final int dstsz) final WindowCursor curs) throws IOException, DataFormatException {
throws IOException, DataFormatException { byte[] dstbuf;
if (curs.inflate(this, position, dstbuf, dstoff) != dstsz) try {
dstbuf = new byte[sz];
} catch (OutOfMemoryError noMemory) {
// The size may be larger than our heap allows, return null to
// let the caller know allocation isn't possible and it should
// use the large object streaming approach instead.
//
// For example, this can occur when sz is 640 MB, and JRE
// maximum heap size is only 256 MB. Even if the JRE has
// 200 MB free, it cannot allocate a 640 MB byte array.
return null;
}
if (curs.inflate(this, position, dstbuf, 0) != sz)
throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position)); throw new EOFException(MessageFormat.format(JGitText.get().shortCompressedStreamAt, position));
return dstbuf;
} }
final void copyAsIs(PackOutputStream out, LocalObjectToPack src, final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
@ -608,62 +621,138 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
, getPackFile())); , getPackFile()));
} }
ObjectLoader load(final WindowCursor curs, final long pos) ObjectLoader load(final WindowCursor curs, long pos)
throws IOException { throws IOException {
final byte[] ib = curs.tempId;
readFully(pos, ib, 0, 20, curs);
int c = ib[0] & 0xff;
final int type = (c >> 4) & 7;
long sz = c & 15;
int shift = 4;
int p = 1;
while ((c & 0x80) != 0) {
c = ib[p++] & 0xff;
sz += (c & 0x7f) << shift;
shift += 7;
}
try { try {
switch (type) { final byte[] ib = curs.tempId;
case Constants.OBJ_COMMIT: Delta delta = null;
case Constants.OBJ_TREE: byte[] data = null;
case Constants.OBJ_BLOB: int type = Constants.OBJ_BAD;
case Constants.OBJ_TAG: { boolean cached = false;
if (sz < curs.getStreamFileThreshold()) {
byte[] data; SEARCH: for (;;) {
try { readFully(pos, ib, 0, 20, curs);
data = new byte[(int) sz]; int c = ib[0] & 0xff;
} catch (OutOfMemoryError tooBig) { final int typeCode = (c >> 4) & 7;
return largeWhole(curs, pos, type, sz, p); long sz = c & 15;
int shift = 4;
int p = 1;
while ((c & 0x80) != 0) {
c = ib[p++] & 0xff;
sz += (c & 0x7f) << shift;
shift += 7;
}
switch (typeCode) {
case Constants.OBJ_COMMIT:
case Constants.OBJ_TREE:
case Constants.OBJ_BLOB:
case Constants.OBJ_TAG: {
if (sz < curs.getStreamFileThreshold())
data = decompress(pos + p, (int) sz, curs);
if (delta != null) {
type = typeCode;
break SEARCH;
} }
decompress(pos + p, curs, data, 0, data.length);
return new ObjectLoader.SmallObject(type, data); if (data != null)
return new ObjectLoader.SmallObject(typeCode, data);
else
return new LargePackedWholeObject(typeCode, sz, pos, p,
this, curs.db);
} }
return largeWhole(curs, pos, type, sz, p);
}
case Constants.OBJ_OFS_DELTA: { case Constants.OBJ_OFS_DELTA: {
c = ib[p++] & 0xff;
long ofs = c & 127;
while ((c & 128) != 0) {
ofs += 1;
c = ib[p++] & 0xff; c = ib[p++] & 0xff;
ofs <<= 7; long base = c & 127;
ofs += (c & 127); while ((c & 128) != 0) {
base += 1;
c = ib[p++] & 0xff;
base <<= 7;
base += (c & 127);
}
base = pos - base;
delta = new Delta(delta, pos, (int) sz, p, base);
if (sz != delta.deltaSize)
break SEARCH;
DeltaBaseCache.Entry e = DeltaBaseCache.get(this, base);
if (e != null) {
type = e.type;
data = e.data;
cached = true;
break SEARCH;
}
pos = base;
continue SEARCH;
} }
return loadDelta(pos, p, sz, pos - ofs, curs);
}
case Constants.OBJ_REF_DELTA: { case Constants.OBJ_REF_DELTA: {
readFully(pos + p, ib, 0, 20, curs); readFully(pos + p, ib, 0, 20, curs);
long ofs = findDeltaBase(ObjectId.fromRaw(ib)); long base = findDeltaBase(ObjectId.fromRaw(ib));
return loadDelta(pos, p + 20, sz, ofs, curs); delta = new Delta(delta, pos, (int) sz, p + 20, base);
} if (sz != delta.deltaSize)
break SEARCH;
DeltaBaseCache.Entry e = DeltaBaseCache.get(this, base);
if (e != null) {
type = e.type;
data = e.data;
cached = true;
break SEARCH;
}
pos = base;
continue SEARCH;
}
default: default:
throw new IOException(MessageFormat.format( throw new IOException(MessageFormat.format(
JGitText.get().unknownObjectType, type)); JGitText.get().unknownObjectType, typeCode));
}
} }
// At this point there is at least one delta to apply to data.
// (Whole objects with no deltas to apply return early above.)
if (data == null)
return delta.large(this, curs);
do {
// Cache only the base immediately before desired object.
if (cached)
cached = false;
else if (delta.next == null)
DeltaBaseCache.store(this, delta.basePos, data, type);
pos = delta.deltaPos;
final byte[] cmds = decompress(pos + delta.hdrLen,
delta.deltaSize, curs);
if (cmds == null) {
data = null; // Discard base in case of OutOfMemoryError
return delta.large(this, curs);
}
final long sz = BinaryDelta.getResultSize(cmds);
if (Integer.MAX_VALUE <= sz)
return delta.large(this, curs);
final byte[] result;
try {
result = new byte[(int) sz];
} catch (OutOfMemoryError tooBig) {
data = null; // Discard base in case of OutOfMemoryError
return delta.large(this, curs);
}
BinaryDelta.apply(data, cmds, result);
data = result;
delta = delta.next;
} while (delta != null);
return new ObjectLoader.SmallObject(type, data);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
CorruptObjectException coe = new CorruptObjectException( CorruptObjectException coe = new CorruptObjectException(
MessageFormat.format( MessageFormat.format(
@ -683,61 +772,41 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
return ofs; return ofs;
} }
private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz, private static class Delta {
long posBase, WindowCursor curs) throws IOException, /** Child that applies onto this object. */
DataFormatException { final Delta next;
if (Integer.MAX_VALUE <= sz)
return largeDelta(posSelf, hdrLen, posBase, curs);
byte[] base; /** Offset of the delta object. */
int type; final long deltaPos;
DeltaBaseCache.Entry e = DeltaBaseCache.get(this, posBase); /** Size of the inflated delta stream. */
if (e != null) { final int deltaSize;
base = e.data;
type = e.type;
} else {
ObjectLoader p = load(curs, posBase);
try {
base = p.getCachedBytes(curs.getStreamFileThreshold());
} catch (LargeObjectException tooBig) {
return largeDelta(posSelf, hdrLen, posBase, curs);
}
type = p.getType();
DeltaBaseCache.store(this, posBase, base, type);
}
final byte[] delta; /** Total size of the delta's pack entry header (including base). */
try { final int hdrLen;
delta = new byte[(int) sz];
} catch (OutOfMemoryError tooBig) {
return largeDelta(posSelf, hdrLen, posBase, curs);
}
decompress(posSelf + hdrLen, curs, delta, 0, delta.length); /** Offset of the base object this delta applies onto. */
sz = BinaryDelta.getResultSize(delta); final long basePos;
if (Integer.MAX_VALUE <= sz)
return largeDelta(posSelf, hdrLen, posBase, curs);
final byte[] result; Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
try { this.next = next;
result = new byte[(int) sz]; this.deltaPos = ofs;
} catch (OutOfMemoryError tooBig) { this.deltaSize = sz;
return largeDelta(posSelf, hdrLen, posBase, curs); this.hdrLen = hdrLen;
this.basePos = baseOffset;
} }
BinaryDelta.apply(base, delta, result); ObjectLoader large(PackFile pack, WindowCursor wc) {
return new ObjectLoader.SmallObject(type, result); Delta d = this;
} while (d.next != null)
d = d.next;
private LargePackedWholeObject largeWhole(final WindowCursor curs, return d.newLargeLoader(pack, wc);
final long pos, final int type, long sz, int p) { }
return new LargePackedWholeObject(type, sz, pos, p, this, curs.db);
}
private LargePackedDeltaObject largeDelta(long posObj, int hdrLen, private ObjectLoader newLargeLoader(PackFile pack, WindowCursor wc) {
long posBase, WindowCursor wc) { return new LargePackedDeltaObject(deltaPos, basePos, hdrLen,
return new LargePackedDeltaObject(posObj, posBase, hdrLen, this, wc.db); pack, wc.db);
}
} }
byte[] getDeltaHeader(WindowCursor wc, long pos) byte[] getDeltaHeader(WindowCursor wc, long pos)

Loading…
Cancel
Save