blob: 46607f60d955458b2ec894df42cb11505dca164b [file] [log] [blame]
/*
* 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.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();
}
@Override
void delete(RefDirectoryUpdate update) throws IOException {
refreshSnapshot();
super.delete(update);
}
@Override
public RefDirectoryUpdate newUpdate(String name, boolean detach)
throws IOException {
refreshSnapshot();
return super.newUpdate(name, detach);
}
@Override
public PackedBatchRefUpdate newBatchUpdate() {
return new SnapshotPackedBatchRefUpdate(this);
}
@Override
public PackedBatchRefUpdate newBatchUpdate(boolean shouldLockLooseRefs) {
return new SnapshotPackedBatchRefUpdate(this, shouldLockLooseRefs);
}
@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
* if an IO error occurred
*/
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, SnapshottingRefDirectory refDb)
throws IOException {
return invalidateSnapshotOnError(a -> f.call(), null, refDb);
}
private static <A, R> R invalidateSnapshotOnError(
FunctionThrowsException<A, R, IOException> f, A a,
SnapshottingRefDirectory refDb) throws IOException {
try {
return f.apply(a);
} catch (IOException e) {
refDb.invalidateSnapshot();
throw e;
}
}
private static <A1, A2, A3> void invalidateSnapshotOnError(
TriConsumerThrowsException<A1, A2, A3, IOException> f, A1 a1, A2 a2,
A3 a3, SnapshottingRefDirectory refDb) throws IOException {
try {
f.accept(a1, a2, a3);
} catch (IOException e) {
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(super::update, walk,
getRefDatabase());
}
@Override
public Result delete() throws IOException {
return invalidateSnapshotOnError(super::delete, getRefDatabase());
}
@Override
public Result delete(RevWalk walk) throws IOException {
return invalidateSnapshotOnError(super::delete, walk,
getRefDatabase());
}
@Override
public Result link(String target) throws IOException {
return invalidateSnapshotOnError(super::link, target,
getRefDatabase());
}
/**
* Invalidate the SnapshottingRefDirectory snapshot after locking the
* ref.
* <p>
* Doing this after locking the ref ensures that the upcoming write is
* not based on a cached value.
*
* @param name
* the name of the reference.
*/
@Override
protected void doAfterLocking(String name) {
getRefDatabase().invalidateSnapshot();
}
@Override
public SnapshottingRefDirectory getRefDatabase() {
return (SnapshottingRefDirectory) super.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());
}
@Override
public SnapshottingRefDirectory getRefDirectory() {
return (SnapshottingRefDirectory) super.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(super::execute,
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());
}
@Override
public SnapshottingRefDirectory getRefDatabase() {
return (SnapshottingRefDirectory) super.getRefDatabase();
}
}
}