diff options
author | Kaushik Lingarkar <quic_kaushikl@quicinc.com> | 2023-02-27 15:29:00 -0800 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2023-03-30 22:25:53 +0200 |
commit | 33c00f3347738b73d5b09a2a882ec9a9aac5a6af (patch) | |
tree | 1097508b2f9a2ecfc8d65ab61291c4f2f3326921 /org.eclipse.jgit/src/org/eclipse/jgit | |
parent | d7400517bf2744937746ac2912c93eb8d172ed28 (diff) | |
download | jgit-33c00f3347738b73d5b09a2a882ec9a9aac5a6af.tar.gz jgit-33c00f3347738b73d5b09a2a882ec9a9aac5a6af.zip |
Implement a snapshotting RefDirectory for use in request scope
Introduce a SnapshottingRefDirectory class which allows users to get
a snapshot of the ref database and use it in a request scope (for
example a Gerrit query) instead of having to re-read packed-refs
several times in a request.
This can potentially be further improved to avoid scanning/reading a
loose ref several times in a request. This would especially help
repeated lookups of a packed ref, where we check for the existence of
a loose ref each time.
Change-Id: I634b92877f819f8bf36a3b9586bbc1815108189a
Signed-off-by: Kaushik Lingarkar <quic_kaushikl@quicinc.com>
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit')
4 files changed, 315 insertions, 2 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 72f1d5afa2..cc9db5f77d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -179,6 +179,19 @@ public class RefDirectory extends RefDatabase { private final TrustPackedRefsStat trustPackedRefsStat; + RefDirectory(RefDirectory refDb) { + parent = refDb.parent; + gitDir = refDb.gitDir; + refsDir = refDb.refsDir; + logsDir = refDb.logsDir; + logsRefsDir = refDb.logsRefsDir; + packedRefsFile = refDb.packedRefsFile; + looseRefs.set(refDb.looseRefs.get()); + packedRefs.set(refDb.packedRefs.get()); + trustFolderStat = refDb.trustFolderStat; + trustPackedRefsStat = refDb.trustPackedRefsStat; + } + RefDirectory(FileRepository db) { final FS fs = db.getFS(); parent = db; @@ -223,6 +236,15 @@ public class RefDirectory extends RefDatabase { return new File(logsDir, name); } + /** + * Create a cache of this {@link RefDirectory}. + * + * @return a cached RefDirectory. + */ + public SnapshottingRefDirectory createSnapshottingRefDirectory() { + return new SnapshottingRefDirectory(this); + } + /** {@inheritDoc} */ @Override public void create() throws IOException { @@ -575,18 +597,26 @@ public class RefDirectory extends RefDatabase { else { detachingSymbolicRef = detach && ref.isSymbolic(); } - RefDirectoryUpdate refDirUpdate = new RefDirectoryUpdate(this, ref); + RefDirectoryUpdate refDirUpdate = createRefDirectoryUpdate(ref); if (detachingSymbolicRef) refDirUpdate.setDetachingSymbolicRef(); return refDirUpdate; } + RefDirectoryUpdate createRefDirectoryUpdate(Ref ref) { + return new RefDirectoryUpdate(this, ref); + } + /** {@inheritDoc} */ @Override public RefDirectoryRename newRename(String fromName, String toName) throws IOException { RefDirectoryUpdate from = newUpdate(fromName, false); RefDirectoryUpdate to = newUpdate(toName, false); + return createRefDirectoryRename(from, to); + } + + RefDirectoryRename createRefDirectoryRename(RefDirectoryUpdate from, RefDirectoryUpdate to) { return new RefDirectoryRename(from, to); } @@ -966,6 +996,13 @@ public class RefDirectory extends RefDatabase { } } + void compareAndSetPackedRefs(PackedRefList curList, PackedRefList newList) { + if (packedRefs.compareAndSet(curList, newList) + && !curList.id.equals(newList.id)) { + modCnt.incrementAndGet(); + } + } + private RefList<Ref> parsePackedRefs(BufferedReader br) throws IOException { RefList.Builder<Ref> all = new RefList.Builder<>(); @@ -1258,7 +1295,7 @@ public class RefDirectory extends RefDatabase { File tmp = File.createTempFile("renamed_", "_ref", refsDir); //$NON-NLS-1$ //$NON-NLS-2$ String name = Constants.R_REFS + tmp.getName(); Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null); - return new RefDirectoryUpdate(this, ref); + return createRefDirectoryUpdate(ref); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java index 2c0ade681b..d07299e45a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java @@ -59,6 +59,15 @@ class RefDirectoryRename extends RefRename { refdb = src.getRefDatabase(); } + /** + * Get the ref directory associated with this rename. + * + * @return the ref directory. + */ + protected RefDirectory getRefDirectory() { + return refdb; + } + /** {@inheritDoc} */ @Override protected Result doRename() throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java new file mode 100644 index 0000000000..0b9748096e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. + * 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 v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.internal.storage.file; + +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevWalk; + +import java.io.IOException; +import java.util.List; + +/** + * Snapshotting write-through cache of a {@link RefDirectory}. + * <p> + * This is intended to be short-term write-through snapshot based cache used in + * a request scope to avoid re-reading packed-refs on each read. A future + * improvement could also snapshot loose refs. + * <p> + * Only use this class when concurrent writes from other requests (not using the + * same instance of SnapshottingRefDirectory) generally need not be visible to + * the current request. The exception to this is when such writes would cause + * writes from this snapshot to fail due to their base ref value being + * outdated. + */ +class SnapshottingRefDirectory extends RefDirectory { + final RefDirectory refDb; + + private volatile boolean isValid; + + /** + * Create a snapshotting write-through cache of a {@link RefDirectory}. + * + * @param refDb + * a reference to the ref database + */ + SnapshottingRefDirectory(RefDirectory refDb) { + super(refDb); + this.refDb = refDb; + } + + /** + * Lazily initializes and returns a PackedRefList snapshot. + * <p> + * A newer snapshot will be returned when a ref update is performed using + * this {@link SnapshottingRefDirectory}. + */ + @Override + PackedRefList getPackedRefs() throws IOException { + if (!isValid) { + synchronized (this) { + if (!isValid) { + refreshSnapshot(); + } + } + } + return packedRefs.get(); + } + + /** {@inheritDoc} */ + @Override + void delete(RefDirectoryUpdate update) throws IOException { + refreshSnapshot(); + super.delete(update); + } + + /** {@inheritDoc} */ + @Override + public RefDirectoryUpdate newUpdate(String name, boolean detach) + throws IOException { + refreshSnapshot(); + return super.newUpdate(name, detach); + } + + /** {@inheritDoc} */ + @Override + public PackedBatchRefUpdate newBatchUpdate() { + return new SnapshotPackedBatchRefUpdate(this); + } + + /** {@inheritDoc} */ + @Override + public PackedBatchRefUpdate newBatchUpdate(boolean shouldLockLooseRefs) { + return new SnapshotPackedBatchRefUpdate(this, shouldLockLooseRefs); + } + + /** {@inheritDoc} */ + @Override + RefDirectoryUpdate newTemporaryUpdate() throws IOException { + refreshSnapshot(); + return super.newTemporaryUpdate(); + } + + @Override + RefDirectoryUpdate createRefDirectoryUpdate(Ref ref) { + return new SnapshotRefDirectoryUpdate(this, ref); + } + + @Override + RefDirectoryRename createRefDirectoryRename(RefDirectoryUpdate from, + RefDirectoryUpdate to) { + return new SnapshotRefDirectoryRename(from, to); + } + + synchronized void invalidateSnapshot() { + isValid = false; + } + + /** + * Refresh our snapshot by calling the underlying RefDirectory's + * getPackedRefs(). + * <p> + * Update the in-memory copy of the underlying RefDirectory's packed-refs to + * avoid the overhead of re-reading packed-refs on each new snapshot as the + * packed-refs of the underlying RefDirectory may not get updated if most + * threads use this snapshot. + * + * @throws IOException + */ + private synchronized void refreshSnapshot() throws IOException { + compareAndSetPackedRefs(packedRefs.get(), refDb.getPackedRefs()); + isValid = true; + } + + @FunctionalInterface + private interface SupplierThrowsException<R, E extends Exception> { + R call() throws E; + } + + @FunctionalInterface + private interface FunctionThrowsException<A, R, E extends Exception> { + R apply(A a) throws E; + } + + @FunctionalInterface + private interface TriConsumerThrowsException<A1, A2, A3, E extends Exception> { + void accept(A1 a1, A2 a2, A3 a3) throws E; + } + + private static <T> T invalidateSnapshotOnError( + SupplierThrowsException<T, IOException> f, RefDatabase refDb) + throws IOException { + return invalidateSnapshotOnError(a -> f.call(), null, refDb); + } + + private static <A, R> R invalidateSnapshotOnError( + FunctionThrowsException<A, R, IOException> f, A a, + RefDatabase refDb) throws IOException { + try { + return f.apply(a); + } catch (IOException e) { + ((SnapshottingRefDirectory) refDb).invalidateSnapshot(); + throw e; + } + } + + private static <A1, A2, A3> void invalidateSnapshotOnError( + TriConsumerThrowsException<A1, A2, A3, IOException> f, A1 a1, A2 a2, + A3 a3, RefDatabase refDb) throws IOException { + try { + f.accept(a1, a2, a3); + } catch (IOException e) { + ((SnapshottingRefDirectory) refDb).invalidateSnapshot(); + throw e; + } + } + + private static class SnapshotRefDirectoryUpdate extends RefDirectoryUpdate { + SnapshotRefDirectoryUpdate(RefDirectory r, Ref ref) { + super(r, ref); + } + + @Override + public Result forceUpdate() throws IOException { + return invalidateSnapshotOnError(() -> super.forceUpdate(), + getRefDatabase()); + } + + @Override + public Result update() throws IOException { + return invalidateSnapshotOnError(() -> super.update(), + getRefDatabase()); + } + + @Override + public Result update(RevWalk walk) throws IOException { + return invalidateSnapshotOnError(rw -> super.update(rw), walk, + getRefDatabase()); + } + + @Override + public Result delete() throws IOException { + return invalidateSnapshotOnError(() -> super.delete(), + getRefDatabase()); + } + + @Override + public Result delete(RevWalk walk) throws IOException { + return invalidateSnapshotOnError(rw -> super.delete(rw), walk, + getRefDatabase()); + } + + @Override + public Result link(String target) throws IOException { + return invalidateSnapshotOnError(t -> super.link(t), target, + getRefDatabase()); + } + } + + private static class SnapshotRefDirectoryRename extends RefDirectoryRename { + SnapshotRefDirectoryRename(RefDirectoryUpdate src, + RefDirectoryUpdate dst) { + super(src, dst); + } + + @Override + public RefUpdate.Result rename() throws IOException { + return invalidateSnapshotOnError(() -> super.rename(), + getRefDirectory()); + } + } + + private static class SnapshotPackedBatchRefUpdate + extends PackedBatchRefUpdate { + SnapshotPackedBatchRefUpdate(RefDirectory refdb) { + super(refdb); + } + + SnapshotPackedBatchRefUpdate(RefDirectory refdb, + boolean shouldLockLooseRefs) { + super(refdb, shouldLockLooseRefs); + } + + @Override + public void execute(RevWalk walk, ProgressMonitor monitor, + List<String> options) throws IOException { + invalidateSnapshotOnError((rw, m, o) -> super.execute(rw, m, o), + walk, monitor, options, getRefDatabase()); + } + + @Override + public void execute(RevWalk walk, ProgressMonitor monitor) + throws IOException { + invalidateSnapshotOnError((rw, m, a3) -> super.execute(rw, m), walk, + monitor, null, getRefDatabase()); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java index e2bebfefdb..21d77f47c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java @@ -521,6 +521,15 @@ public class BatchRefUpdate { monitor.endTask(); } + /** + * Get the ref database associated with this update. + * + * @return the ref database. + */ + protected RefDatabase getRefDatabase() { + return refdb; + } + private static boolean isMissing(RevWalk walk, ObjectId id) throws IOException { if (id.equals(ObjectId.zeroId())) { |