aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit
diff options
context:
space:
mode:
authorKaushik Lingarkar <quic_kaushikl@quicinc.com>2023-02-27 15:29:00 -0800
committerMatthias Sohn <matthias.sohn@sap.com>2023-03-30 22:25:53 +0200
commit33c00f3347738b73d5b09a2a882ec9a9aac5a6af (patch)
tree1097508b2f9a2ecfc8d65ab61291c4f2f3326921 /org.eclipse.jgit/src/org/eclipse/jgit
parentd7400517bf2744937746ac2912c93eb8d172ed28 (diff)
downloadjgit-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')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java41
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java258
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java9
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())) {