aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorDave Borowitz <dborowitz@google.com>2017-07-28 10:48:25 -0400
committerDave Borowitz <dborowitz@google.com>2017-07-28 11:03:32 -0400
commit45da0fc6f7de6db43725e865e134c42bdffa7df9 (patch)
tree91e2313caf8a34f2edfa8fd8a6826dadaa3c4b66 /org.eclipse.jgit
parent6f23210781666506bde36b48cf00ffc9506348b0 (diff)
downloadjgit-45da0fc6f7de6db43725e865e134c42bdffa7df9.tar.gz
jgit-45da0fc6f7de6db43725e865e134c42bdffa7df9.zip
RefDirectory: Add in-process fair lock for atomic updates
In a server scenario such as Gerrit Code Review, there may be many atomic BatchRefUpdates contending for locks on both the packed-refs file and some subset of loose refs. We already retry lock acquisition to improve this situation slightly, but we can do better by using an in-process lock. This way, instead of retrying and potentially exceeding their timeout, different threads sharing the same Repository instance can wait on a fair lock without having to touch the disk lock. Since a server is probably already using RepositoryCache anyway, there is a high likelihood of reusing the Repository instance. Change-Id: If5dd1dc58f0ce62f26131fd5965a0e21a80e8bd3
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java198
2 files changed, 118 insertions, 87 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
index e71977dcce..b661ae78cf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -177,6 +177,7 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
}
Map<String, LockFile> locks = null;
+ refdb.inProcessPackedRefsLock.lock();
try {
locks = lockLooseRefs(pending);
if (locks == null) {
@@ -195,7 +196,11 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
// commitPackedRefs removes lock file (by renaming over real file).
refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList);
} finally {
- unlockAll(locks);
+ try {
+ unlockAll(locks);
+ } finally {
+ refdb.inProcessPackedRefsLock.unlock();
+ }
}
refdb.fireRefsChanged();
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 20944ad319..ecf7ef942e 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
@@ -75,6 +75,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
@@ -167,6 +168,22 @@ public class RefDirectory extends RefDatabase {
final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
/**
+ * Lock for coordinating operations within a single process that may contend
+ * on the {@code packed-refs} file.
+ * <p>
+ * All operations that write {@code packed-refs} must still acquire a
+ * {@link LockFile} on {@link #packedRefsFile}, even after they have acquired
+ * this lock, since there may be multiple {@link RefDirectory} instances or
+ * other processes operating on the same repo on disk.
+ * <p>
+ * This lock exists so multiple threads in the same process can wait in a fair
+ * queue without trying, failing, and retrying to acquire the on-disk lock. If
+ * {@code RepositoryCache} is used, this lock instance will be used by all
+ * threads.
+ */
+ final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
+
+ /**
* Number of modifications made to this database.
* <p>
* This counter is incremented when a change is made, or detected from the
@@ -610,14 +627,19 @@ public class RefDirectory extends RefDatabase {
// we don't miss an edit made externally.
final PackedRefList packed = getPackedRefs();
if (packed.contains(name)) {
- LockFile lck = lockPackedRefsOrThrow();
+ inProcessPackedRefsLock.lock();
try {
- PackedRefList cur = readPackedRefs();
- int idx = cur.find(name);
- if (0 <= idx)
- commitPackedRefs(lck, cur.remove(idx), packed);
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ PackedRefList cur = readPackedRefs();
+ int idx = cur.find(name);
+ if (0 <= idx)
+ commitPackedRefs(lck, cur.remove(idx), packed);
+ } finally {
+ lck.unlock();
+ }
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}
@@ -671,99 +693,103 @@ public class RefDirectory extends RefDatabase {
FS fs = parent.getFS();
// Lock the packed refs file and read the content
- LockFile lck = lockPackedRefsOrThrow();
-
+ inProcessPackedRefsLock.lock();
try {
- final PackedRefList packed = getPackedRefs();
- RefList<Ref> cur = readPackedRefs();
-
- // Iterate over all refs to be packed
- boolean dirty = false;
- for (String refName : refs) {
- Ref oldRef = readRef(refName, cur);
- if (oldRef == null) {
- continue; // A non-existent ref is already correctly packed.
- }
- if (oldRef.isSymbolic()) {
- continue; // can't pack symbolic refs
- }
- // Add/Update it to packed-refs
- Ref newRef = peeledPackedRef(oldRef);
- if (newRef == oldRef) {
- // No-op; peeledPackedRef returns the input ref only if it's already
- // packed, and readRef returns a packed ref only if there is no loose
- // ref.
- continue;
- }
+ LockFile lck = lockPackedRefsOrThrow();
+ try {
+ final PackedRefList packed = getPackedRefs();
+ RefList<Ref> cur = readPackedRefs();
+
+ // Iterate over all refs to be packed
+ boolean dirty = false;
+ for (String refName : refs) {
+ Ref oldRef = readRef(refName, cur);
+ if (oldRef == null) {
+ continue; // A non-existent ref is already correctly packed.
+ }
+ if (oldRef.isSymbolic()) {
+ continue; // can't pack symbolic refs
+ }
+ // Add/Update it to packed-refs
+ Ref newRef = peeledPackedRef(oldRef);
+ if (newRef == oldRef) {
+ // No-op; peeledPackedRef returns the input ref only if it's already
+ // packed, and readRef returns a packed ref only if there is no
+ // loose ref.
+ continue;
+ }
- dirty = true;
- int idx = cur.find(refName);
- if (idx >= 0) {
- cur = cur.set(idx, newRef);
- } else {
- cur = cur.add(idx, newRef);
+ dirty = true;
+ int idx = cur.find(refName);
+ if (idx >= 0) {
+ cur = cur.set(idx, newRef);
+ } else {
+ cur = cur.add(idx, newRef);
+ }
}
- }
- if (!dirty) {
- // All requested refs were already packed accurately
- return packed;
- }
-
- // The new content for packed-refs is collected. Persist it.
- PackedRefList result = commitPackedRefs(lck, cur, packed);
-
- // Now delete the loose refs which are now packed
- for (String refName : refs) {
- // Lock the loose ref
- File refFile = fileFor(refName);
- if (!fs.exists(refFile)) {
- continue;
+ if (!dirty) {
+ // All requested refs were already packed accurately
+ return packed;
}
- LockFile rLck = heldLocks.get(refName);
- boolean shouldUnlock;
- if (rLck == null) {
- rLck = new LockFile(refFile);
- if (!rLck.lock()) {
- continue;
- }
- shouldUnlock = true;
- } else {
- shouldUnlock = false;
- }
+ // The new content for packed-refs is collected. Persist it.
+ PackedRefList result = commitPackedRefs(lck, cur, packed);
- try {
- LooseRef currentLooseRef = scanRef(null, refName);
- if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
+ // Now delete the loose refs which are now packed
+ for (String refName : refs) {
+ // Lock the loose ref
+ File refFile = fileFor(refName);
+ if (!fs.exists(refFile)) {
continue;
}
- Ref packedRef = cur.get(refName);
- ObjectId clr_oid = currentLooseRef.getObjectId();
- if (clr_oid != null
- && clr_oid.equals(packedRef.getObjectId())) {
- RefList<LooseRef> curLoose, newLoose;
- do {
- curLoose = looseRefs.get();
- int idx = curLoose.find(refName);
- if (idx < 0) {
- break;
- }
- newLoose = curLoose.remove(idx);
- } while (!looseRefs.compareAndSet(curLoose, newLoose));
- int levels = levelsIn(refName) - 2;
- delete(refFile, levels, rLck);
+
+ LockFile rLck = heldLocks.get(refName);
+ boolean shouldUnlock;
+ if (rLck == null) {
+ rLck = new LockFile(refFile);
+ if (!rLck.lock()) {
+ continue;
+ }
+ shouldUnlock = true;
+ } else {
+ shouldUnlock = false;
}
- } finally {
- if (shouldUnlock) {
- rLck.unlock();
+
+ try {
+ LooseRef currentLooseRef = scanRef(null, refName);
+ if (currentLooseRef == null || currentLooseRef.isSymbolic()) {
+ continue;
+ }
+ Ref packedRef = cur.get(refName);
+ ObjectId clr_oid = currentLooseRef.getObjectId();
+ if (clr_oid != null
+ && clr_oid.equals(packedRef.getObjectId())) {
+ RefList<LooseRef> curLoose, newLoose;
+ do {
+ curLoose = looseRefs.get();
+ int idx = curLoose.find(refName);
+ if (idx < 0) {
+ break;
+ }
+ newLoose = curLoose.remove(idx);
+ } while (!looseRefs.compareAndSet(curLoose, newLoose));
+ int levels = levelsIn(refName) - 2;
+ delete(refFile, levels, rLck);
+ }
+ } finally {
+ if (shouldUnlock) {
+ rLck.unlock();
+ }
}
}
+ // Don't fire refsChanged. The refs have not change, only their
+ // storage.
+ return result;
+ } finally {
+ lck.unlock();
}
- // Don't fire refsChanged. The refs have not change, only their
- // storage.
- return result;
} finally {
- lck.unlock();
+ inProcessPackedRefsLock.unlock();
}
}