Only increment mod count if packed-refs file changes

Previously if a packed-refs file was racily clean then there
was a 2.5 second window in which each call to getPackedRefs
would increment the mod count causing a RefsChangedEvent to be
fired since the FileSnapshot would report the file as modified.

If a RefsChangedListener called getRef/getRefs from the
onRefsChanged method then a StackOverflowError could occur
since the stack could be exhausted before the 2.5 second
window expired and the packed-refs file would no longer
report being modified.

Now a SHA-1 is computed of the packed-refs file and the
mod count is only incremented when the packed refs are
successfully set and the id of the new packed-refs file
does not match the id of the old packed-refs file.

Change-Id: I8cab6e5929479ed748812b8598c7628370e79697
This commit is contained in:
Kevin Sawicki 2012-05-10 19:13:24 -07:00
parent 24a0f47e32
commit 91f5ce3a15
2 changed files with 52 additions and 10 deletions

View File

@ -60,6 +60,8 @@
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
@ -1077,6 +1079,36 @@ public void testPeelCommit() throws IOException {
assertSame(master_p2, refdir.peel(master_p2));
}
@Test
public void testRefsChangedStackOverflow() throws Exception {
final FileRepository newRepo = createBareRepository();
final RefDatabase refDb = newRepo.getRefDatabase();
File packedRefs = new File(newRepo.getDirectory(), "packed-refs");
assertTrue(packedRefs.createNewFile());
final AtomicReference<StackOverflowError> error = new AtomicReference<StackOverflowError>();
final AtomicReference<IOException> exception = new AtomicReference<IOException>();
final AtomicInteger changeCount = new AtomicInteger();
newRepo.getListenerList().addRefsChangedListener(
new RefsChangedListener() {
public void onRefsChanged(RefsChangedEvent event) {
try {
refDb.getRefs("ref");
changeCount.incrementAndGet();
} catch (StackOverflowError soe) {
error.set(soe);
} catch (IOException ioe) {
exception.set(ioe);
}
}
});
refDb.getRefs("ref");
refDb.getRefs("ref");
assertNull(error.get());
assertNull(exception.get());
assertEquals(1, changeCount.get());
}
private void writeLooseRef(String name, AnyObjectId id) throws IOException {
writeLooseRef(name, id.name() + "\n");
}

View File

@ -63,6 +63,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.LinkedList;
@ -626,24 +628,27 @@ private PackedRefList getPackedRefs() throws IOException {
return curList;
final PackedRefList newList = readPackedRefs();
if (packedRefs.compareAndSet(curList, newList))
if (packedRefs.compareAndSet(curList, newList)
&& !curList.id.equals(newList.id))
modCnt.incrementAndGet();
return newList;
}
private PackedRefList readPackedRefs()
throws IOException {
private PackedRefList readPackedRefs() throws IOException {
final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
final BufferedReader br;
final MessageDigest digest = Constants.newMessageDigest();
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(
packedRefsFile), CHARSET));
br = new BufferedReader(new InputStreamReader(
new DigestInputStream(new FileInputStream(packedRefsFile),
digest), CHARSET));
} catch (FileNotFoundException noPackedRefs) {
// Ignore it and leave the new list empty.
return PackedRefList.NO_PACKED_REFS;
}
try {
return new PackedRefList(parsePackedRefs(br), snapshot);
return new PackedRefList(parsePackedRefs(br), snapshot,
ObjectId.fromRaw(digest.digest()));
} finally {
br.close();
}
@ -724,8 +729,9 @@ protected void writeFile(String name, byte[] content)
if (!lck.commit())
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
packedRefs.compareAndSet(oldPackedList, new PackedRefList(
refs, lck.getCommitSnapshot()));
byte[] digest = Constants.newMessageDigest().digest(content);
packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
lck.getCommitSnapshot(), ObjectId.fromRaw(digest)));
}
}.writePackedRefs();
}
@ -899,13 +905,17 @@ static void delete(final File file, final int depth) throws IOException {
private static class PackedRefList extends RefList<Ref> {
static final PackedRefList NO_PACKED_REFS = new PackedRefList(
RefList.emptyList(), FileSnapshot.MISSING_FILE);
RefList.emptyList(), FileSnapshot.MISSING_FILE,
ObjectId.zeroId());
final FileSnapshot snapshot;
PackedRefList(RefList<Ref> src, FileSnapshot s) {
final ObjectId id;
PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
super(src);
snapshot = s;
id = i;
}
}