Browse Source

RepoCommand: Retry commit on LockFailure

When the target repository is receiving commits from other sources,
the repo command commit can fail with a LOCK_FAILURE. We could let
callers retry, but then the command needs to redo all the work (opening
all subrepos to recreate the tree).

Retry the commit in LOCK_FAILURE inside the command. The commit
rewrites the whole tree, so it shouldn't have merge errors. Use an
exponential delay with jitter for the retries.

Change-Id: I517b6f2afd16a4b695e6cf471b5d6cf492024ec4
Signed-off-by: Ivan Frade <ifrade@google.com>
tags/v5.12.0.202106011439-rc1
Ivan Frade 2 years ago
parent
commit
c59626ad7a
1 changed files with 67 additions and 43 deletions
  1. 67
    43
      org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java

+ 67
- 43
org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java View File

@@ -80,6 +80,13 @@ import org.eclipse.jgit.util.FileUtils;
* @since 3.4
*/
public class RepoCommand extends GitCommand<RevCommit> {
private static final int LOCK_FAILURE_MAX_RETRIES = 5;

// Retry exponentially with delays in this range
private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50;

private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000;

private String manifestPath;
private String baseUri;
private URI targetUri;
@@ -686,50 +693,22 @@ public class RepoCommand extends GitCommand<RevCommit> {
builder.finish();
ObjectId treeId = index.writeTree(inserter);

// Create a Commit object, populate it and write it
ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
// No change. Do nothing.
return rw.parseCommit(headId);
}

CommitBuilder commit = new CommitBuilder();
commit.setTreeId(treeId);
if (headId != null)
commit.setParentIds(headId);
commit.setAuthor(author);
commit.setCommitter(author);
commit.setMessage(RepoText.get().repoCommitMessage);

ObjectId commitId = inserter.insert(commit);
inserter.flush();

RefUpdate ru = repo.updateRef(targetBranch);
ru.setNewObjectId(commitId);
ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
Result rc = ru.update(rw);

switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD:
// Successful. Do nothing.
break;
case REJECTED:
case LOCK_FAILURE:
throw new ConcurrentRefUpdateException(
MessageFormat.format(
JGitText.get().cannotLock, targetBranch),
ru.getRef(),
rc);
default:
throw new JGitInternalException(MessageFormat.format(
JGitText.get().updatingRefFailed,
targetBranch, commitId.name(), rc));
long prevDelay = 0;
for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) {
try {
return commitTreeOnCurrentTip(
inserter, rw, treeId);
} catch (ConcurrentRefUpdateException e) {
prevDelay = FileUtils.delay(prevDelay,
LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS,
LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS);
Thread.sleep(prevDelay);
repo.getRefDatabase().refresh();
}
}
return rw.parseCommit(commitId);
} catch (GitAPIException | IOException e) {
// In the last try, just propagate the exceptions
return commitTreeOnCurrentTip(inserter, rw, treeId);
} catch (GitAPIException | IOException | InterruptedException e) {
throw new ManifestErrorException(e);
}
}
@@ -746,6 +725,51 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
}


private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter,
RevWalk rw, ObjectId treeId)
throws IOException, ConcurrentRefUpdateException {
ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$
if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) {
// No change. Do nothing.
return rw.parseCommit(headId);
}

CommitBuilder commit = new CommitBuilder();
commit.setTreeId(treeId);
if (headId != null)
commit.setParentIds(headId);
commit.setAuthor(author);
commit.setCommitter(author);
commit.setMessage(RepoText.get().repoCommitMessage);

ObjectId commitId = inserter.insert(commit);
inserter.flush();

RefUpdate ru = repo.updateRef(targetBranch);
ru.setNewObjectId(commitId);
ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
Result rc = ru.update(rw);
switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD:
// Successful. Do nothing.
break;
case REJECTED:
case LOCK_FAILURE:
throw new ConcurrentRefUpdateException(MessageFormat
.format(JGitText.get().cannotLock, targetBranch),
ru.getRef(), rc);
default:
throw new JGitInternalException(MessageFormat.format(
JGitText.get().updatingRefFailed,
targetBranch, commitId.name(), rc));
}

return rw.parseCommit(commitId);
}

private void addSubmodule(String name, String url, String path,
String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
Git git) throws GitAPIException, IOException {

Loading…
Cancel
Save