Merge changes I3668a396,I18f48321,I121f356c
* changes: Unpack and cache large deltas as loose objects Remember loose objects and fast-track their lookup Correctly name DeltaBaseCache
This commit is contained in:
commit
0e8ef77840
|
@ -54,7 +54,6 @@
|
|||
import org.eclipse.jgit.lib.ObjectDatabase;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectIdSubclassMap;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
import org.eclipse.jgit.storage.pack.ObjectToPack;
|
||||
import org.eclipse.jgit.storage.pack.PackWriter;
|
||||
|
@ -113,7 +112,7 @@ public void close() {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter newInserter() {
|
||||
public ObjectDirectoryInserter newInserter() {
|
||||
return wrapped.newInserter();
|
||||
}
|
||||
|
||||
|
@ -213,6 +212,11 @@ long getObjectSize2(WindowCursor curs, String objectName, AnyObjectId objectId)
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean insertUnpackedObject(File tmp, ObjectId objectId, boolean force) {
|
||||
return wrapped.insertUnpackedObject(tmp, objectId, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
|
||||
WindowCursor curs) throws IOException {
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
* under the terms of the Eclipse Distribution License v1.0 which
|
||||
* accompanies this distribution, is reproduced below, and is
|
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.eclipse.jgit.storage.file;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
|
||||
class DeltaBaseCache {
|
||||
private static final int CACHE_SZ = 1024;
|
||||
|
||||
private static final SoftReference<Entry> DEAD;
|
||||
|
||||
private static int hash(final long position) {
|
||||
return (((int) position) << 22) >>> 22;
|
||||
}
|
||||
|
||||
private static int maxByteCount;
|
||||
|
||||
private static final Slot[] cache;
|
||||
|
||||
private static Slot lruHead;
|
||||
|
||||
private static Slot lruTail;
|
||||
|
||||
private static int openByteCount;
|
||||
|
||||
static {
|
||||
DEAD = new SoftReference<Entry>(null);
|
||||
maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit();
|
||||
|
||||
cache = new Slot[CACHE_SZ];
|
||||
for (int i = 0; i < CACHE_SZ; i++)
|
||||
cache[i] = new Slot();
|
||||
}
|
||||
|
||||
static synchronized void reconfigure(final WindowCacheConfig cfg) {
|
||||
final int dbLimit = cfg.getDeltaBaseCacheLimit();
|
||||
if (maxByteCount != dbLimit) {
|
||||
maxByteCount = dbLimit;
|
||||
releaseMemory();
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized Entry get(final PackFile pack, final long position) {
|
||||
final Slot e = cache[hash(position)];
|
||||
if (e.provider == pack && e.position == position) {
|
||||
final Entry buf = e.data.get();
|
||||
if (buf != null) {
|
||||
moveToHead(e);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static synchronized void store(final PackFile pack, final long position,
|
||||
final byte[] data, final int objectType) {
|
||||
if (data.length > maxByteCount)
|
||||
return; // Too large to cache.
|
||||
|
||||
final Slot e = cache[hash(position)];
|
||||
clearEntry(e);
|
||||
|
||||
openByteCount += data.length;
|
||||
releaseMemory();
|
||||
|
||||
e.provider = pack;
|
||||
e.position = position;
|
||||
e.sz = data.length;
|
||||
e.data = new SoftReference<Entry>(new Entry(data, objectType));
|
||||
moveToHead(e);
|
||||
}
|
||||
|
||||
private static void releaseMemory() {
|
||||
while (openByteCount > maxByteCount && lruTail != null) {
|
||||
final Slot currOldest = lruTail;
|
||||
final Slot nextOldest = currOldest.lruPrev;
|
||||
|
||||
clearEntry(currOldest);
|
||||
currOldest.lruPrev = null;
|
||||
currOldest.lruNext = null;
|
||||
|
||||
if (nextOldest == null)
|
||||
lruHead = null;
|
||||
else
|
||||
nextOldest.lruNext = null;
|
||||
lruTail = nextOldest;
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized void purge(final PackFile file) {
|
||||
for (final Slot e : cache) {
|
||||
if (e.provider == file) {
|
||||
clearEntry(e);
|
||||
unlink(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveToHead(final Slot e) {
|
||||
unlink(e);
|
||||
e.lruPrev = null;
|
||||
e.lruNext = lruHead;
|
||||
if (lruHead != null)
|
||||
lruHead.lruPrev = e;
|
||||
else
|
||||
lruTail = e;
|
||||
lruHead = e;
|
||||
}
|
||||
|
||||
private static void unlink(final Slot e) {
|
||||
final Slot prev = e.lruPrev;
|
||||
final Slot next = e.lruNext;
|
||||
if (prev != null)
|
||||
prev.lruNext = next;
|
||||
if (next != null)
|
||||
next.lruPrev = prev;
|
||||
}
|
||||
|
||||
private static void clearEntry(final Slot e) {
|
||||
openByteCount -= e.sz;
|
||||
e.provider = null;
|
||||
e.data = DEAD;
|
||||
e.sz = 0;
|
||||
}
|
||||
|
||||
private DeltaBaseCache() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
static class Entry {
|
||||
final byte[] data;
|
||||
|
||||
final int type;
|
||||
|
||||
Entry(final byte[] aData, final int aType) {
|
||||
data = aData;
|
||||
type = aType;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Slot {
|
||||
Slot lruPrev;
|
||||
|
||||
Slot lruNext;
|
||||
|
||||
PackFile provider;
|
||||
|
||||
long position;
|
||||
|
||||
int sz;
|
||||
|
||||
SoftReference<Entry> data = DEAD;
|
||||
}
|
||||
}
|
|
@ -62,6 +62,9 @@ public ObjectReader newReader() {
|
|||
return new WindowCursor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract ObjectDirectoryInserter newInserter();
|
||||
|
||||
/**
|
||||
* Does the requested object exist in this database?
|
||||
* <p>
|
||||
|
@ -246,6 +249,8 @@ abstract long getObjectSize1(WindowCursor curs, AnyObjectId objectId)
|
|||
abstract long getObjectSize2(WindowCursor curs, String objectName,
|
||||
AnyObjectId objectId) throws IOException;
|
||||
|
||||
abstract boolean insertUnpackedObject(File tmp, ObjectId id, boolean force);
|
||||
|
||||
abstract FileObjectDatabase newCachedFileObjectDatabase();
|
||||
|
||||
static class AlternateHandle {
|
||||
|
|
|
@ -44,9 +44,12 @@
|
|||
package org.eclipse.jgit.storage.file;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
|
@ -58,7 +61,6 @@
|
|||
import org.eclipse.jgit.lib.ObjectStream;
|
||||
import org.eclipse.jgit.storage.pack.BinaryDelta;
|
||||
import org.eclipse.jgit.storage.pack.DeltaStream;
|
||||
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||
import org.eclipse.jgit.util.io.TeeInputStream;
|
||||
|
||||
class LargePackedDeltaObject extends ObjectLoader {
|
||||
|
@ -165,14 +167,39 @@ public byte[] getCachedBytes() throws LargeObjectException {
|
|||
|
||||
@Override
|
||||
public ObjectStream openStream() throws MissingObjectException, IOException {
|
||||
// If the object was recently unpacked, its available loose.
|
||||
// The loose format is going to be faster to access than a
|
||||
// delta applied on top of a base. Use that whenever we can.
|
||||
//
|
||||
final ObjectId myId = getObjectId();
|
||||
final WindowCursor wc = new WindowCursor(db);
|
||||
ObjectLoader ldr = db.openObject2(wc, myId.name(), myId);
|
||||
if (ldr != null)
|
||||
return ldr.openStream();
|
||||
|
||||
InputStream in = open(wc);
|
||||
in = new BufferedInputStream(in, 8192);
|
||||
return new ObjectStream.Filter(getType(), size, in) {
|
||||
|
||||
// While we inflate the object, also deflate it back as a loose
|
||||
// object. This will later be cleaned up by a gc pass, but until
|
||||
// then we will reuse the loose form by the above code path.
|
||||
//
|
||||
int myType = getType();
|
||||
long mySize = getSize();
|
||||
final ObjectDirectoryInserter odi = db.newInserter();
|
||||
final File tmp = odi.newTempFile();
|
||||
DeflaterOutputStream dOut = odi.compress(new FileOutputStream(tmp));
|
||||
odi.writeHeader(dOut, myType, mySize);
|
||||
|
||||
in = new TeeInputStream(in, dOut);
|
||||
return new ObjectStream.Filter(myType, mySize, in) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
wc.release();
|
||||
super.close();
|
||||
|
||||
odi.release();
|
||||
wc.release();
|
||||
db.insertUnpackedObject(tmp, myId, true /* force creation */);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -195,13 +222,9 @@ private InputStream open(final WindowCursor wc)
|
|||
final ObjectLoader base = pack.load(wc, baseOffset);
|
||||
DeltaStream ds = new DeltaStream(delta) {
|
||||
private long baseSize = SIZE_UNKNOWN;
|
||||
private TemporaryBuffer.LocalFile buffer;
|
||||
|
||||
@Override
|
||||
protected InputStream openBase() throws IOException {
|
||||
if (buffer != null)
|
||||
return buffer.openInputStream();
|
||||
|
||||
InputStream in;
|
||||
if (base instanceof LargePackedDeltaObject)
|
||||
in = ((LargePackedDeltaObject) base).open(wc);
|
||||
|
@ -213,9 +236,7 @@ protected InputStream openBase() throws IOException {
|
|||
else if (in instanceof ObjectStream)
|
||||
baseSize = ((ObjectStream) in).getSize();
|
||||
}
|
||||
|
||||
buffer = new TemporaryBuffer.LocalFile(db.getDirectory());
|
||||
return new TeeInputStream(in, buffer);
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -228,14 +249,11 @@ protected long getBaseSize() throws IOException {
|
|||
}
|
||||
return baseSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
if (buffer != null)
|
||||
buffer.destroy();
|
||||
}
|
||||
};
|
||||
if (type == Constants.OBJ_BAD) {
|
||||
if (!(base instanceof LargePackedDeltaObject))
|
||||
type = base.getType();
|
||||
}
|
||||
if (size == SIZE_UNKNOWN)
|
||||
size = ds.getSize();
|
||||
return ds;
|
||||
|
|
|
@ -69,7 +69,6 @@
|
|||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectDatabase;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
import org.eclipse.jgit.lib.RepositoryCache;
|
||||
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
|
||||
|
@ -117,6 +116,8 @@ public class ObjectDirectory extends FileObjectDatabase {
|
|||
|
||||
private final AtomicReference<AlternateHandle[]> alternates;
|
||||
|
||||
private final UnpackedObjectCache unpackedObjectCache;
|
||||
|
||||
/**
|
||||
* Initialize a reference to an on-disk object directory.
|
||||
*
|
||||
|
@ -140,6 +141,7 @@ public ObjectDirectory(final Config cfg, final File dir,
|
|||
packDirectory = new File(objects, "pack");
|
||||
alternatesFile = new File(infoDirectory, "alternates");
|
||||
packList = new AtomicReference<PackList>(NO_PACKS);
|
||||
unpackedObjectCache = new UnpackedObjectCache();
|
||||
this.fs = fs;
|
||||
|
||||
alternates = new AtomicReference<AlternateHandle[]>();
|
||||
|
@ -173,12 +175,14 @@ public void create() throws IOException {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ObjectInserter newInserter() {
|
||||
public ObjectDirectoryInserter newInserter() {
|
||||
return new ObjectDirectoryInserter(this, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
unpackedObjectCache.clear();
|
||||
|
||||
final PackList packs = packList.get();
|
||||
packList.set(NO_PACKS);
|
||||
for (final PackFile p : packs.packs)
|
||||
|
@ -255,6 +259,8 @@ public String toString() {
|
|||
}
|
||||
|
||||
boolean hasObject1(final AnyObjectId objectId) {
|
||||
if (unpackedObjectCache.isUnpacked(objectId))
|
||||
return true;
|
||||
for (final PackFile p : packList.get().packs) {
|
||||
try {
|
||||
if (p.hasObject(objectId)) {
|
||||
|
@ -328,6 +334,14 @@ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
|
|||
|
||||
ObjectLoader openObject1(final WindowCursor curs,
|
||||
final AnyObjectId objectId) throws IOException {
|
||||
if (unpackedObjectCache.isUnpacked(objectId)) {
|
||||
ObjectLoader ldr = openObject2(curs, objectId.name(), objectId);
|
||||
if (ldr != null)
|
||||
return ldr;
|
||||
else
|
||||
unpackedObjectCache.remove(objectId);
|
||||
}
|
||||
|
||||
PackList pList = packList.get();
|
||||
SEARCH: for (;;) {
|
||||
for (final PackFile p : pList.packs) {
|
||||
|
@ -429,15 +443,61 @@ ObjectLoader openObject2(final WindowCursor curs,
|
|||
File path = fileFor(objectName);
|
||||
FileInputStream in = new FileInputStream(path);
|
||||
try {
|
||||
unpackedObjectCache.add(objectId);
|
||||
return UnpackedObject.open(in, path, objectId, curs);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} catch (FileNotFoundException noFile) {
|
||||
unpackedObjectCache.remove(objectId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean insertUnpackedObject(File tmp, ObjectId id, boolean force) {
|
||||
if (!force && has(id)) {
|
||||
// Object is already in the repository, remove temporary file.
|
||||
//
|
||||
tmp.delete();
|
||||
return true;
|
||||
}
|
||||
tmp.setReadOnly();
|
||||
|
||||
final File dst = fileFor(id);
|
||||
if (force && dst.exists()) {
|
||||
tmp.delete();
|
||||
return true;
|
||||
}
|
||||
if (tmp.renameTo(dst)) {
|
||||
unpackedObjectCache.add(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Maybe the directory doesn't exist yet as the object
|
||||
// directories are always lazily created. Note that we
|
||||
// try the rename first as the directory likely does exist.
|
||||
//
|
||||
dst.getParentFile().mkdir();
|
||||
if (tmp.renameTo(dst)) {
|
||||
unpackedObjectCache.add(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!force && has(id)) {
|
||||
tmp.delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The object failed to be renamed into its proper
|
||||
// location and it doesn't exist in the repository
|
||||
// either. We really don't know what went wrong, so
|
||||
// fail.
|
||||
//
|
||||
tmp.delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean tryAgain1() {
|
||||
final PackList old = packList.get();
|
||||
if (old.tryAgain(packDirectory.lastModified()))
|
||||
|
|
|
@ -83,36 +83,10 @@ public ObjectId insert(final int type, long len, final InputStream is)
|
|||
final MessageDigest md = digest();
|
||||
final File tmp = toTemp(md, type, len, is);
|
||||
final ObjectId id = ObjectId.fromRaw(md.digest());
|
||||
if (db.has(id)) {
|
||||
// Object is already in the repository, remove temporary file.
|
||||
//
|
||||
tmp.delete();
|
||||
if (db.insertUnpackedObject(tmp, id, false /* no duplicate */))
|
||||
return id;
|
||||
}
|
||||
|
||||
final File dst = db.fileFor(id);
|
||||
if (tmp.renameTo(dst))
|
||||
return id;
|
||||
|
||||
// Maybe the directory doesn't exist yet as the object
|
||||
// directories are always lazily created. Note that we
|
||||
// try the rename first as the directory likely does exist.
|
||||
//
|
||||
dst.getParentFile().mkdir();
|
||||
if (tmp.renameTo(dst))
|
||||
return id;
|
||||
|
||||
if (db.has(id)) {
|
||||
tmp.delete();
|
||||
return id;
|
||||
}
|
||||
|
||||
// The object failed to be renamed into its proper
|
||||
// location and it doesn't exist in the repository
|
||||
// either. We really don't know what went wrong, so
|
||||
// fail.
|
||||
//
|
||||
tmp.delete();
|
||||
throw new ObjectWritingException("Unable to create new object: " + dst);
|
||||
}
|
||||
|
||||
|
@ -136,15 +110,12 @@ private File toTemp(final MessageDigest md, final int type, long len,
|
|||
final InputStream is) throws IOException, FileNotFoundException,
|
||||
Error {
|
||||
boolean delete = true;
|
||||
File tmp = File.createTempFile("noz", null, db.getDirectory());
|
||||
File tmp = newTempFile();
|
||||
try {
|
||||
DigestOutputStream dOut = new DigestOutputStream(
|
||||
compress(new FileOutputStream(tmp)), md);
|
||||
try {
|
||||
dOut.write(Constants.encodedTypeString(type));
|
||||
dOut.write((byte) ' ');
|
||||
dOut.write(Constants.encodeASCII(len));
|
||||
dOut.write((byte) 0);
|
||||
writeHeader(dOut, type, len);
|
||||
|
||||
final byte[] buf = buffer();
|
||||
while (len > 0) {
|
||||
|
@ -158,7 +129,6 @@ private File toTemp(final MessageDigest md, final int type, long len,
|
|||
dOut.close();
|
||||
}
|
||||
|
||||
tmp.setReadOnly();
|
||||
delete = false;
|
||||
return tmp;
|
||||
} finally {
|
||||
|
@ -167,7 +137,19 @@ private File toTemp(final MessageDigest md, final int type, long len,
|
|||
}
|
||||
}
|
||||
|
||||
private DeflaterOutputStream compress(final OutputStream out) {
|
||||
void writeHeader(OutputStream out, final int type, long len)
|
||||
throws IOException {
|
||||
out.write(Constants.encodedTypeString(type));
|
||||
out.write((byte) ' ');
|
||||
out.write(Constants.encodeASCII(len));
|
||||
out.write((byte) 0);
|
||||
}
|
||||
|
||||
File newTempFile() throws IOException {
|
||||
return File.createTempFile("noz", null, db.getDirectory());
|
||||
}
|
||||
|
||||
DeflaterOutputStream compress(final OutputStream out) {
|
||||
if (deflate == null)
|
||||
deflate = new Deflater(config.get(CoreConfig.KEY).getCompression());
|
||||
else
|
||||
|
|
|
@ -220,7 +220,7 @@ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit)
|
|||
* Close the resources utilized by this repository
|
||||
*/
|
||||
public void close() {
|
||||
UnpackedObjectCache.purge(this);
|
||||
DeltaBaseCache.purge(this);
|
||||
WindowCache.purge(this);
|
||||
synchronized (this) {
|
||||
loadedIdx = null;
|
||||
|
@ -274,14 +274,6 @@ ObjectId findObjectForOffset(final long offset) throws IOException {
|
|||
return getReverseIdx().findObject(offset);
|
||||
}
|
||||
|
||||
private final UnpackedObjectCache.Entry readCache(final long position) {
|
||||
return UnpackedObjectCache.get(this, position);
|
||||
}
|
||||
|
||||
private final void saveCache(final long position, final byte[] data, final int type) {
|
||||
UnpackedObjectCache.store(this, position, data, type);
|
||||
}
|
||||
|
||||
private final byte[] decompress(final long position, final long totalSize,
|
||||
final WindowCursor curs) throws IOException, DataFormatException {
|
||||
final byte[] dstbuf = new byte[(int) totalSize];
|
||||
|
@ -700,7 +692,7 @@ private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz,
|
|||
byte[] data;
|
||||
int type;
|
||||
|
||||
UnpackedObjectCache.Entry e = readCache(posBase);
|
||||
DeltaBaseCache.Entry e = DeltaBaseCache.get(this, posBase);
|
||||
if (e != null) {
|
||||
data = e.data;
|
||||
type = e.type;
|
||||
|
@ -715,7 +707,7 @@ private ObjectLoader loadDelta(long posSelf, int hdrLen, long sz,
|
|||
}
|
||||
data = p.getCachedBytes();
|
||||
type = p.getType();
|
||||
saveCache(posBase, data, type);
|
||||
DeltaBaseCache.store(this, posBase, data, type);
|
||||
}
|
||||
|
||||
// At this point we have the base, and its small, and the delta
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
|
||||
* Copyright (C) 2010, Google Inc.
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
|
@ -43,153 +43,107 @@
|
|||
|
||||
package org.eclipse.jgit.storage.file;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||
|
||||
import org.eclipse.jgit.lib.AnyObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
/** Remembers objects that are currently unpacked. */
|
||||
class UnpackedObjectCache {
|
||||
private static final int CACHE_SZ = 1024;
|
||||
private static final int INITIAL_BITS = 5; // size = 32
|
||||
|
||||
private static final SoftReference<Entry> DEAD;
|
||||
private static final int MAX_BITS = 11; // size = 2048
|
||||
|
||||
private static int hash(final long position) {
|
||||
return (((int) position) << 22) >>> 22;
|
||||
private volatile Table table;
|
||||
|
||||
UnpackedObjectCache() {
|
||||
table = new Table(INITIAL_BITS);
|
||||
}
|
||||
|
||||
private static int maxByteCount;
|
||||
|
||||
private static final Slot[] cache;
|
||||
|
||||
private static Slot lruHead;
|
||||
|
||||
private static Slot lruTail;
|
||||
|
||||
private static int openByteCount;
|
||||
|
||||
static {
|
||||
DEAD = new SoftReference<Entry>(null);
|
||||
maxByteCount = new WindowCacheConfig().getDeltaBaseCacheLimit();
|
||||
|
||||
cache = new Slot[CACHE_SZ];
|
||||
for (int i = 0; i < CACHE_SZ; i++)
|
||||
cache[i] = new Slot();
|
||||
boolean isUnpacked(AnyObjectId objectId) {
|
||||
return table.contains(objectId);
|
||||
}
|
||||
|
||||
static synchronized void reconfigure(final WindowCacheConfig cfg) {
|
||||
final int dbLimit = cfg.getDeltaBaseCacheLimit();
|
||||
if (maxByteCount != dbLimit) {
|
||||
maxByteCount = dbLimit;
|
||||
releaseMemory();
|
||||
void add(AnyObjectId objectId) {
|
||||
Table t = table;
|
||||
if (t.add(objectId)) {
|
||||
// The object either already exists in the table, or was
|
||||
// successfully added. Either way leave the table alone.
|
||||
//
|
||||
} else {
|
||||
// The object won't fit into the table. Implement a crude
|
||||
// cache removal by just dropping the table away, but double
|
||||
// it in size for the next incarnation.
|
||||
//
|
||||
Table n = new Table(Math.min(t.bits + 1, MAX_BITS));
|
||||
n.add(objectId);
|
||||
table = n;
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized Entry get(final PackFile pack, final long position) {
|
||||
final Slot e = cache[hash(position)];
|
||||
if (e.provider == pack && e.position == position) {
|
||||
final Entry buf = e.data.get();
|
||||
if (buf != null) {
|
||||
moveToHead(e);
|
||||
return buf;
|
||||
void remove(AnyObjectId objectId) {
|
||||
if (isUnpacked(objectId))
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
table = new Table(INITIAL_BITS);
|
||||
}
|
||||
|
||||
private static class Table {
|
||||
private static final int MAX_CHAIN = 8;
|
||||
|
||||
private final AtomicReferenceArray<ObjectId> ids;
|
||||
|
||||
private final int shift;
|
||||
|
||||
final int bits;
|
||||
|
||||
Table(int bits) {
|
||||
this.ids = new AtomicReferenceArray<ObjectId>(1 << bits);
|
||||
this.shift = 32 - bits;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
boolean contains(AnyObjectId toFind) {
|
||||
int i = index(toFind);
|
||||
for (int n = 0; n < MAX_CHAIN; n++) {
|
||||
ObjectId obj = ids.get(i);
|
||||
if (obj == null)
|
||||
break;
|
||||
|
||||
if (AnyObjectId.equals(obj, toFind))
|
||||
return true;
|
||||
|
||||
if (++i == ids.length())
|
||||
i = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static synchronized void store(final PackFile pack, final long position,
|
||||
final byte[] data, final int objectType) {
|
||||
if (data.length > maxByteCount)
|
||||
return; // Too large to cache.
|
||||
boolean add(AnyObjectId toAdd) {
|
||||
int i = index(toAdd);
|
||||
for (int n = 0; n < MAX_CHAIN;) {
|
||||
ObjectId obj = ids.get(i);
|
||||
if (obj == null) {
|
||||
if (ids.compareAndSet(i, null, toAdd.copy()))
|
||||
return true;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
final Slot e = cache[hash(position)];
|
||||
clearEntry(e);
|
||||
if (AnyObjectId.equals(obj, toAdd))
|
||||
return true;
|
||||
|
||||
openByteCount += data.length;
|
||||
releaseMemory();
|
||||
|
||||
e.provider = pack;
|
||||
e.position = position;
|
||||
e.sz = data.length;
|
||||
e.data = new SoftReference<Entry>(new Entry(data, objectType));
|
||||
moveToHead(e);
|
||||
}
|
||||
|
||||
private static void releaseMemory() {
|
||||
while (openByteCount > maxByteCount && lruTail != null) {
|
||||
final Slot currOldest = lruTail;
|
||||
final Slot nextOldest = currOldest.lruPrev;
|
||||
|
||||
clearEntry(currOldest);
|
||||
currOldest.lruPrev = null;
|
||||
currOldest.lruNext = null;
|
||||
|
||||
if (nextOldest == null)
|
||||
lruHead = null;
|
||||
else
|
||||
nextOldest.lruNext = null;
|
||||
lruTail = nextOldest;
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized void purge(final PackFile file) {
|
||||
for (final Slot e : cache) {
|
||||
if (e.provider == file) {
|
||||
clearEntry(e);
|
||||
unlink(e);
|
||||
if (++i == ids.length())
|
||||
i = 0;
|
||||
n++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveToHead(final Slot e) {
|
||||
unlink(e);
|
||||
e.lruPrev = null;
|
||||
e.lruNext = lruHead;
|
||||
if (lruHead != null)
|
||||
lruHead.lruPrev = e;
|
||||
else
|
||||
lruTail = e;
|
||||
lruHead = e;
|
||||
}
|
||||
|
||||
private static void unlink(final Slot e) {
|
||||
final Slot prev = e.lruPrev;
|
||||
final Slot next = e.lruNext;
|
||||
if (prev != null)
|
||||
prev.lruNext = next;
|
||||
if (next != null)
|
||||
next.lruPrev = prev;
|
||||
}
|
||||
|
||||
private static void clearEntry(final Slot e) {
|
||||
openByteCount -= e.sz;
|
||||
e.provider = null;
|
||||
e.data = DEAD;
|
||||
e.sz = 0;
|
||||
}
|
||||
|
||||
private UnpackedObjectCache() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
static class Entry {
|
||||
final byte[] data;
|
||||
|
||||
final int type;
|
||||
|
||||
Entry(final byte[] aData, final int aType) {
|
||||
data = aData;
|
||||
type = aType;
|
||||
private int index(AnyObjectId id) {
|
||||
return id.hashCode() >>> shift;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Slot {
|
||||
Slot lruPrev;
|
||||
|
||||
Slot lruNext;
|
||||
|
||||
PackFile provider;
|
||||
|
||||
long position;
|
||||
|
||||
int sz;
|
||||
|
||||
SoftReference<Entry> data = DEAD;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ public static void reconfigure(final WindowCacheConfig cfg) {
|
|||
oc.removeAll();
|
||||
cache = nc;
|
||||
streamFileThreshold = cfg.getStreamFileThreshold();
|
||||
UnpackedObjectCache.reconfigure(cfg);
|
||||
DeltaBaseCache.reconfigure(cfg);
|
||||
}
|
||||
|
||||
static int getStreamFileThreshold() {
|
||||
|
|
|
@ -146,7 +146,7 @@ public void setPackedGitMMAP(final boolean usemmap) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return maximum number of bytes to cache in {@link UnpackedObjectCache}
|
||||
* @return maximum number of bytes to cache in {@link DeltaBaseCache}
|
||||
* for inflated, recently accessed objects, without delta chains.
|
||||
* <b>Default 10 MB.</b>
|
||||
*/
|
||||
|
@ -157,7 +157,7 @@ public int getDeltaBaseCacheLimit() {
|
|||
/**
|
||||
* @param newLimit
|
||||
* maximum number of bytes to cache in
|
||||
* {@link UnpackedObjectCache} for inflated, recently accessed
|
||||
* {@link DeltaBaseCache} for inflated, recently accessed
|
||||
* objects, without delta chains.
|
||||
*/
|
||||
public void setDeltaBaseCacheLimit(final int newLimit) {
|
||||
|
|
Loading…
Reference in New Issue