RefDirectory: Retry acquiring ref locks with backoff
If a repo frequently uses PackedBatchRefUpdates, there is likely to be contention on the packed-refs file, so it's not appropriate to fail immediately the first time we fail to acquire a lock. Add some logic to RefDirectory to support general retrying of lock acquisition. Currently, there is a hard-coded wait starting at 100ms and backing off exponentially to 1600ms, for about 3s of total wait. This is no worse than the hard-coded backoff that JGit does elsewhere, e.g. in FileUtils#delete. One can imagine a scheme that uses per-repository configuration of backoff, and the current interface would support this without changing any callers. Change-Id: I4764e11270d9336882483eb698f67a78a401c251
This commit is contained in:
parent
5188c23104
commit
6f23210781
|
@ -54,6 +54,7 @@
|
||||||
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
|
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
|
||||||
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
|
import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -96,6 +97,7 @@
|
||||||
import org.junit.runners.Parameterized.Parameter;
|
import org.junit.runners.Parameterized.Parameter;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
@SuppressWarnings("boxing")
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
|
public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
|
||||||
@Parameter
|
@Parameter
|
||||||
|
@ -125,6 +127,7 @@ public void setUp() throws Exception {
|
||||||
cfg.save();
|
cfg.save();
|
||||||
|
|
||||||
refdir = (RefDirectory) diskRepo.getRefDatabase();
|
refdir = (RefDirectory) diskRepo.getRefDatabase();
|
||||||
|
refdir.setRetrySleepMs(Arrays.asList(0, 0));
|
||||||
|
|
||||||
repo = new TestRepository<>(diskRepo);
|
repo = new TestRepository<>(diskRepo);
|
||||||
A = repo.commit().create();
|
A = repo.commit().create();
|
||||||
|
@ -584,6 +587,85 @@ public void overrideDisableRefLog() throws Exception {
|
||||||
getLastReflog("refs/heads/branch"));
|
getLastReflog("refs/heads/branch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void packedRefsLockFailure() throws Exception {
|
||||||
|
writeLooseRef("refs/heads/master", A);
|
||||||
|
|
||||||
|
List<ReceiveCommand> cmds = Arrays.asList(
|
||||||
|
new ReceiveCommand(A, B, "refs/heads/master", UPDATE),
|
||||||
|
new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE));
|
||||||
|
|
||||||
|
LockFile myLock = refdir.lockPackedRefs();
|
||||||
|
try {
|
||||||
|
execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
|
||||||
|
|
||||||
|
assertFalse(getLockFile("refs/heads/master").exists());
|
||||||
|
assertFalse(getLockFile("refs/heads/branch").exists());
|
||||||
|
|
||||||
|
if (atomic) {
|
||||||
|
assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED);
|
||||||
|
assertRefs("refs/heads/master", A);
|
||||||
|
} else {
|
||||||
|
// Only operates on loose refs, doesn't care that packed-refs is locked.
|
||||||
|
assertResults(cmds, OK, OK);
|
||||||
|
assertRefs(
|
||||||
|
"refs/heads/master", B,
|
||||||
|
"refs/heads/branch", B);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
myLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oneRefLockFailure() throws Exception {
|
||||||
|
writeLooseRef("refs/heads/master", A);
|
||||||
|
|
||||||
|
List<ReceiveCommand> cmds = Arrays.asList(
|
||||||
|
new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE),
|
||||||
|
new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
|
||||||
|
|
||||||
|
LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master"));
|
||||||
|
assertTrue(myLock.lock());
|
||||||
|
try {
|
||||||
|
execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
|
||||||
|
|
||||||
|
assertFalse(LockFile.getLockFile(refdir.packedRefsFile).exists());
|
||||||
|
assertFalse(getLockFile("refs/heads/branch").exists());
|
||||||
|
|
||||||
|
if (atomic) {
|
||||||
|
assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE);
|
||||||
|
assertRefs("refs/heads/master", A);
|
||||||
|
} else {
|
||||||
|
assertResults(cmds, OK, LOCK_FAILURE);
|
||||||
|
assertRefs(
|
||||||
|
"refs/heads/branch", B,
|
||||||
|
"refs/heads/master", A);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
myLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception {
|
||||||
|
writeLooseRef("refs/heads/master", A);
|
||||||
|
|
||||||
|
List<ReceiveCommand> cmds = Arrays.asList(
|
||||||
|
new ReceiveCommand(A, B, "refs/heads/master", UPDATE));
|
||||||
|
|
||||||
|
LockFile myLock = refdir.lockPackedRefs();
|
||||||
|
try {
|
||||||
|
execute(newBatchUpdate(cmds).setAllowNonFastForwards(true));
|
||||||
|
|
||||||
|
assertFalse(getLockFile("refs/heads/master").exists());
|
||||||
|
assertResults(cmds, OK);
|
||||||
|
assertRefs("refs/heads/master", B);
|
||||||
|
} finally {
|
||||||
|
myLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
|
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
|
||||||
write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
|
write(new File(diskRepo.getDirectory(), name), id.name() + "\n");
|
||||||
}
|
}
|
||||||
|
@ -712,6 +794,10 @@ private ReflogEntry getLastReflog(String name) throws IOException {
|
||||||
return r.getLastEntry();
|
return r.getLastEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File getLockFile(String refName) {
|
||||||
|
return LockFile.getLockFile(refdir.fileFor(refName));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertReflogUnchanged(
|
private void assertReflogUnchanged(
|
||||||
Map<String, ReflogEntry> old, String name) throws IOException {
|
Map<String, ReflogEntry> old, String name) throws IOException {
|
||||||
assertReflogEquals(old.get(name), getLastReflog(name), true);
|
assertReflogEquals(old.get(name), getLastReflog(name), true);
|
||||||
|
|
|
@ -60,10 +60,12 @@
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.LockFailedException;
|
||||||
import org.eclipse.jgit.events.ListenerHandle;
|
import org.eclipse.jgit.events.ListenerHandle;
|
||||||
import org.eclipse.jgit.events.RefsChangedEvent;
|
import org.eclipse.jgit.events.RefsChangedEvent;
|
||||||
import org.eclipse.jgit.events.RefsChangedListener;
|
import org.eclipse.jgit.events.RefsChangedListener;
|
||||||
|
@ -79,6 +81,7 @@
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@SuppressWarnings("boxing")
|
||||||
public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
|
public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
|
||||||
private Repository diskRepo;
|
private Repository diskRepo;
|
||||||
|
|
||||||
|
@ -1284,6 +1287,23 @@ public void onRefsChanged(RefsChangedEvent event) {
|
||||||
assertEquals(1, changeCount.get());
|
assertEquals(1, changeCount.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPackedRefsLockFailure() throws Exception {
|
||||||
|
writeLooseRef("refs/heads/master", A);
|
||||||
|
refdir.setRetrySleepMs(Arrays.asList(0, 0));
|
||||||
|
LockFile myLock = refdir.lockPackedRefs();
|
||||||
|
try {
|
||||||
|
refdir.pack(Arrays.asList("refs/heads/master"));
|
||||||
|
fail("expected LockFailedException");
|
||||||
|
} catch (LockFailedException e) {
|
||||||
|
assertEquals(refdir.packedRefsFile.getPath(), e.getFile().getPath());
|
||||||
|
} finally {
|
||||||
|
myLock.unlock();
|
||||||
|
}
|
||||||
|
Ref ref = refdir.getRef("refs/heads/master");
|
||||||
|
assertEquals(Storage.LOOSE, ref.getStorage());
|
||||||
|
}
|
||||||
|
|
||||||
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
|
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
|
||||||
writeLooseRef(name, id.name() + "\n");
|
writeLooseRef(name, id.name() + "\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
|
import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -57,7 +58,10 @@
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
|
import org.eclipse.jgit.errors.LockFailedException;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
|
import org.eclipse.jgit.internal.storage.file.RefDirectory.PackedRefList;
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
@ -164,12 +168,18 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
|
||||||
|
|
||||||
// Pack refs normally, so we can create lock files even in the case where
|
// Pack refs normally, so we can create lock files even in the case where
|
||||||
// refs/x is deleted and refs/x/y is created in this batch.
|
// refs/x is deleted and refs/x/y is created in this batch.
|
||||||
refdb.pack(
|
|
||||||
pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
|
|
||||||
|
|
||||||
Map<String, LockFile> locks = new HashMap<>();
|
|
||||||
try {
|
try {
|
||||||
if (!lockLooseRefs(pending, locks)) {
|
refdb.pack(
|
||||||
|
pending.stream().map(ReceiveCommand::getRefName).collect(toList()));
|
||||||
|
} catch (LockFailedException e) {
|
||||||
|
lockFailure(pending.get(0), pending);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, LockFile> locks = null;
|
||||||
|
try {
|
||||||
|
locks = lockLooseRefs(pending);
|
||||||
|
if (locks == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PackedRefList oldPackedList = refdb.pack(locks);
|
PackedRefList oldPackedList = refdb.pack(locks);
|
||||||
|
@ -177,15 +187,15 @@ public void execute(RevWalk walk, ProgressMonitor monitor,
|
||||||
if (newRefs == null) {
|
if (newRefs == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LockFile packedRefsLock = new LockFile(refdb.packedRefsFile);
|
LockFile packedRefsLock = refdb.lockPackedRefs();
|
||||||
try {
|
if (packedRefsLock == null) {
|
||||||
packedRefsLock.lock();
|
lockFailure(pending.get(0), pending);
|
||||||
refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList);
|
return;
|
||||||
} finally {
|
|
||||||
packedRefsLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
// commitPackedRefs removes lock file (by renaming over real file).
|
||||||
|
refdb.commitPackedRefs(packedRefsLock, newRefs, oldPackedList);
|
||||||
} finally {
|
} finally {
|
||||||
locks.values().forEach(LockFile::unlock);
|
unlockAll(locks);
|
||||||
}
|
}
|
||||||
|
|
||||||
refdb.fireRefsChanged();
|
refdb.fireRefsChanged();
|
||||||
|
@ -271,17 +281,54 @@ private boolean checkNonFastForwards(RevWalk walk,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean lockLooseRefs(List<ReceiveCommand> commands,
|
/**
|
||||||
Map<String, LockFile> locks) throws IOException {
|
* Lock loose refs corresponding to a list of commands.
|
||||||
for (ReceiveCommand c : commands) {
|
*
|
||||||
LockFile lock = new LockFile(refdb.fileFor(c.getRefName()));
|
* @param commands
|
||||||
if (!lock.lock()) {
|
* commands that we intend to execute.
|
||||||
lockFailure(c, commands);
|
* @return map of ref name in the input commands to lock file. Always contains
|
||||||
return false;
|
* one entry for each ref in the input list. All locks are acquired
|
||||||
|
* before returning. If any lock was not able to be acquired: the
|
||||||
|
* return value is null; no locks are held; and all commands that were
|
||||||
|
* pending are set to fail with {@code LOCK_FAILURE}.
|
||||||
|
* @throws IOException
|
||||||
|
* an error occurred other than a failure to acquire; no locks are
|
||||||
|
* held if this exception is thrown.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
|
||||||
|
throws IOException {
|
||||||
|
ReceiveCommand failed = null;
|
||||||
|
Map<String, LockFile> locks = new HashMap<>();
|
||||||
|
try {
|
||||||
|
RETRY: for (int ms : refdb.getRetrySleepMs()) {
|
||||||
|
failed = null;
|
||||||
|
// Release all locks before trying again, to prevent deadlock.
|
||||||
|
unlockAll(locks);
|
||||||
|
locks.clear();
|
||||||
|
RefDirectory.sleep(ms);
|
||||||
|
|
||||||
|
for (ReceiveCommand c : commands) {
|
||||||
|
String name = c.getRefName();
|
||||||
|
LockFile lock = new LockFile(refdb.fileFor(name));
|
||||||
|
if (locks.put(name, lock) != null) {
|
||||||
|
throw new IOException(
|
||||||
|
MessageFormat.format(JGitText.get().duplicateRef, name));
|
||||||
|
}
|
||||||
|
if (!lock.lock()) {
|
||||||
|
failed = c;
|
||||||
|
continue RETRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, LockFile> result = locks;
|
||||||
|
locks = null;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
locks.put(c.getRefName(), lock);
|
} finally {
|
||||||
|
unlockAll(locks);
|
||||||
}
|
}
|
||||||
return true;
|
lockFailure(failed != null ? failed : commands.get(0), commands);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
|
private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
|
||||||
|
@ -444,6 +491,12 @@ private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
|
||||||
Ref.Storage.PACKED, cmd.getRefName(), newId);
|
Ref.Storage.PACKED, cmd.getRefName(), newId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void unlockAll(@Nullable Map<?, LockFile> locks) {
|
||||||
|
if (locks != null) {
|
||||||
|
locks.values().forEach(LockFile::unlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void lockFailure(ReceiveCommand cmd,
|
private static void lockFailure(ReceiveCommand cmd,
|
||||||
List<ReceiveCommand> commands) {
|
List<ReceiveCommand> commands) {
|
||||||
reject(cmd, LOCK_FAILURE, commands);
|
reject(cmd, LOCK_FAILURE, commands);
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
import org.eclipse.jgit.errors.InvalidObjectIdException;
|
import org.eclipse.jgit.errors.InvalidObjectIdException;
|
||||||
import org.eclipse.jgit.errors.LockFailedException;
|
import org.eclipse.jgit.errors.LockFailedException;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
@ -137,6 +139,10 @@ public class RefDirectory extends RefDatabase {
|
||||||
Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
|
Constants.MERGE_HEAD, Constants.FETCH_HEAD, Constants.ORIG_HEAD,
|
||||||
Constants.CHERRY_PICK_HEAD };
|
Constants.CHERRY_PICK_HEAD };
|
||||||
|
|
||||||
|
@SuppressWarnings("boxing")
|
||||||
|
private static final List<Integer> RETRY_SLEEP_MS =
|
||||||
|
Collections.unmodifiableList(Arrays.asList(0, 100, 200, 400, 800, 1600));
|
||||||
|
|
||||||
private final FileRepository parent;
|
private final FileRepository parent;
|
||||||
|
|
||||||
private final File gitDir;
|
private final File gitDir;
|
||||||
|
@ -176,6 +182,8 @@ public class RefDirectory extends RefDatabase {
|
||||||
*/
|
*/
|
||||||
private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
|
private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
|
||||||
|
|
||||||
|
private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
|
||||||
|
|
||||||
RefDirectory(final FileRepository db) {
|
RefDirectory(final FileRepository db) {
|
||||||
final FS fs = db.getFS();
|
final FS fs = db.getFS();
|
||||||
parent = db;
|
parent = db;
|
||||||
|
@ -602,9 +610,7 @@ void delete(RefDirectoryUpdate update) throws IOException {
|
||||||
// we don't miss an edit made externally.
|
// we don't miss an edit made externally.
|
||||||
final PackedRefList packed = getPackedRefs();
|
final PackedRefList packed = getPackedRefs();
|
||||||
if (packed.contains(name)) {
|
if (packed.contains(name)) {
|
||||||
LockFile lck = new LockFile(packedRefsFile);
|
LockFile lck = lockPackedRefsOrThrow();
|
||||||
if (!lck.lock())
|
|
||||||
throw new LockFailedException(packedRefsFile);
|
|
||||||
try {
|
try {
|
||||||
PackedRefList cur = readPackedRefs();
|
PackedRefList cur = readPackedRefs();
|
||||||
int idx = cur.find(name);
|
int idx = cur.find(name);
|
||||||
|
@ -665,11 +671,7 @@ private PackedRefList pack(Collection<String> refs,
|
||||||
FS fs = parent.getFS();
|
FS fs = parent.getFS();
|
||||||
|
|
||||||
// Lock the packed refs file and read the content
|
// Lock the packed refs file and read the content
|
||||||
LockFile lck = new LockFile(packedRefsFile);
|
LockFile lck = lockPackedRefsOrThrow();
|
||||||
if (!lck.lock()) {
|
|
||||||
throw new IOException(MessageFormat.format(
|
|
||||||
JGitText.get().cannotLock, packedRefsFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final PackedRefList packed = getPackedRefs();
|
final PackedRefList packed = getPackedRefs();
|
||||||
|
@ -765,6 +767,26 @@ private PackedRefList pack(Collection<String> refs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
LockFile lockPackedRefs() throws IOException {
|
||||||
|
LockFile lck = new LockFile(packedRefsFile);
|
||||||
|
for (int ms : getRetrySleepMs()) {
|
||||||
|
sleep(ms);
|
||||||
|
if (lck.lock()) {
|
||||||
|
return lck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LockFile lockPackedRefsOrThrow() throws IOException {
|
||||||
|
LockFile lck = lockPackedRefs();
|
||||||
|
if (lck == null) {
|
||||||
|
throw new LockFailedException(packedRefsFile);
|
||||||
|
}
|
||||||
|
return lck;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure a ref is peeled and has the Storage PACKED. If the given ref
|
* Make sure a ref is peeled and has the Storage PACKED. If the given ref
|
||||||
* has this attributes simply return it. Otherwise create a new peeled
|
* has this attributes simply return it. Otherwise create a new peeled
|
||||||
|
@ -1175,6 +1197,63 @@ private static void delete(final File file, final int depth, LockFile rLck)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get times to sleep while retrying a possibly contentious operation.
|
||||||
|
* <p>
|
||||||
|
* For retrying an operation that might have high contention, such as locking
|
||||||
|
* the {@code packed-refs} file, the caller may implement a retry loop using
|
||||||
|
* the returned values:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* for (int toSleepMs : getRetrySleepMs()) {
|
||||||
|
* sleep(toSleepMs);
|
||||||
|
* if (isSuccessful(doSomething())) {
|
||||||
|
* return success;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* return failure;
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The first value in the returned iterable is 0, and the caller should treat
|
||||||
|
* a fully-consumed iterator as a timeout.
|
||||||
|
*
|
||||||
|
* @return iterable of times, in milliseconds, that the caller should sleep
|
||||||
|
* before attempting an operation.
|
||||||
|
*/
|
||||||
|
Iterable<Integer> getRetrySleepMs() {
|
||||||
|
return retrySleepMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRetrySleepMs(List<Integer> retrySleepMs) {
|
||||||
|
if (retrySleepMs == null || retrySleepMs.isEmpty()
|
||||||
|
|| retrySleepMs.get(0).intValue() != 0) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
this.retrySleepMs = retrySleepMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleep with {@link Thread#sleep(long)}, converting {@link
|
||||||
|
* InterruptedException} to {@link InterruptedIOException}.
|
||||||
|
*
|
||||||
|
* @param ms
|
||||||
|
* time to sleep, in milliseconds; zero or negative is a no-op.
|
||||||
|
* @throws InterruptedIOException
|
||||||
|
* if sleeping was interrupted.
|
||||||
|
*/
|
||||||
|
static void sleep(long ms) throws InterruptedIOException {
|
||||||
|
if (ms <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(ms);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
InterruptedIOException ie = new InterruptedIOException();
|
||||||
|
ie.initCause(e);
|
||||||
|
throw ie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class PackedRefList extends RefList<Ref> {
|
static class PackedRefList extends RefList<Ref> {
|
||||||
|
|
||||||
private final FileSnapshot snapshot;
|
private final FileSnapshot snapshot;
|
||||||
|
|
Loading…
Reference in New Issue