Skip to content

Commit 02c8c75

Browse files
committed
Merge branch 'stable-6.10' into stable-7.0
* stable-6.10: Avoid reading packed-refs concurrently sometimes Improve flaky InterruptTimerTests Change-Id: I299308d9035e84b9857c6ba38cc3aa7286515177
2 parents b35ad6e + 8b23132 commit 02c8c75

File tree

2 files changed

+115
-23
lines changed

2 files changed

+115
-23
lines changed

org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/InterruptTimerTest.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
import org.junit.Test;
2222

2323
public class InterruptTimerTest {
24-
private static final int MULTIPLIER = 1; // Increase if tests get flaky
24+
private static final int MULTIPLIER = 8; // Increase if tests get flaky
2525
private static final int BUFFER = 5; // Increase if tests get flaky
2626
private static final int REPEATS = 100; // Increase to stress test more
2727

28-
private static final int TOO_LONG = 3 * MULTIPLIER + BUFFER;
29-
private static final int SHORT_ENOUGH = 1 * MULTIPLIER;
28+
private static final int SHORT_ENOUGH = 1;
29+
private static final int TOO_LONG = SHORT_ENOUGH * MULTIPLIER + BUFFER;
3030
private static final int TIMEOUT_LONG_ENOUGH = TOO_LONG;
3131
private static final int TIMEOUT_TOO_SHORT = SHORT_ENOUGH;
3232

@@ -53,6 +53,7 @@ public void testShortEnough() {
5353
timer.end();
5454
} catch (InterruptedException e) {
5555
interrupted++;
56+
Thread.currentThread().interrupt();
5657
}
5758
assertEquals("Was Not Interrupted", interrupted, 0);
5859
}
@@ -66,6 +67,7 @@ public void testTooLong() {
6667
timer.end();
6768
} catch (InterruptedException e) {
6869
interrupted++;
70+
Thread.currentThread().interrupt();
6971
}
7072
assertEquals("Was Interrupted", interrupted, 1);
7173
}
@@ -80,6 +82,7 @@ public void testNotInterruptedAfterEnd() {
8082
Thread.sleep(TIMEOUT_LONG_ENOUGH * 3);
8183
} catch (InterruptedException e) {
8284
interrupted++;
85+
Thread.currentThread().interrupt();
8386
}
8487
assertEquals("Was Not Interrupted Even After End", interrupted, 0);
8588
}
@@ -96,6 +99,7 @@ public void testRestartBeforeTimeout() {
9699
timer.end();
97100
} catch (InterruptedException e) {
98101
interrupted++;
102+
Thread.currentThread().interrupt();
99103
}
100104
assertEquals("Was Not Interrupted Even When Restarted Before Timeout", interrupted, 0);
101105
}
@@ -112,6 +116,7 @@ public void testSecondExpiresBeforeFirst() {
112116
timer.end();
113117
} catch (InterruptedException e) {
114118
interrupted++;
119+
Thread.currentThread().interrupt();
115120
}
116121
assertEquals("Was Interrupted Even When Second Timeout Expired Before First", interrupted, 1);
117122
}
@@ -126,6 +131,7 @@ public void testRepeatedShortEnough() {
126131
timer.end();
127132
} catch (InterruptedException e) {
128133
interrupted++;
134+
Thread.currentThread().interrupt();
129135
}
130136
}
131137
assertEquals("Was Never Interrupted", interrupted, 0);
@@ -140,8 +146,8 @@ public void testRepeatedTooLong() {
140146
Thread.sleep(TOO_LONG);
141147
timer.end();
142148
} catch (InterruptedException e) {
143-
Thread.currentThread().interrupt();
144149
interrupted++;
150+
Thread.currentThread().interrupt();
145151
}
146152
}
147153
assertEquals("Was always Interrupted", interrupted, REPEATS);
@@ -157,6 +163,7 @@ public void testRepeatedShortThanTooLong() {
157163
timer.end();
158164
} catch (InterruptedException e) {
159165
interrupted++;
166+
Thread.currentThread().interrupt();
160167
}
161168
}
162169
assertEquals("Was Not Interrupted Early", interrupted, 0);
@@ -166,6 +173,7 @@ public void testRepeatedShortThanTooLong() {
166173
timer.end();
167174
} catch (InterruptedException e) {
168175
interrupted++;
176+
Thread.currentThread().interrupt();
169177
}
170178
assertEquals("Was Interrupted On Long", interrupted, 1);
171179
}

org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
import java.util.HashMap;
5050
import java.util.List;
5151
import java.util.Map;
52+
import java.util.concurrent.ExecutionException;
53+
import java.util.concurrent.FutureTask;
5254
import java.util.concurrent.atomic.AtomicInteger;
5355
import java.util.concurrent.atomic.AtomicReference;
5456
import java.util.concurrent.locks.ReentrantLock;
@@ -148,6 +150,9 @@ public class RefDirectory extends RefDatabase {
148150
/** Immutable sorted list of packed references. */
149151
final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
150152

153+
private final AtomicReference<PackedRefsRefresher> packedRefsRefresher =
154+
new AtomicReference<>();
155+
151156
/**
152157
* Lock for coordinating operations within a single process that may contend
153158
* on the {@code packed-refs} file.
@@ -948,8 +953,40 @@ else if (0 <= (idx = packed.find(dst.getName())))
948953
}
949954

950955
PackedRefList getPackedRefs() throws IOException {
951-
final PackedRefList curList = packedRefs.get();
956+
PackedRefList curList = packedRefs.get();
957+
if (!curList.shouldRefresh()) {
958+
return curList;
959+
}
960+
return getPackedRefsRefresher(curList).getOrThrowIOException();
961+
}
952962

963+
private PackedRefsRefresher getPackedRefsRefresher(PackedRefList curList)
964+
throws IOException {
965+
PackedRefsRefresher refresher = packedRefsRefresher.get();
966+
if (refresher != null && !refresher.shouldRefresh()) {
967+
return refresher;
968+
}
969+
// This synchronized is NOT needed for correctness. Instead it is used
970+
// as a throttling mechanism to ensure that only one "read" thread does
971+
// the work to refresh the file. In order to avoid stalling writes which
972+
// must already be serialized and tend to be a bottleneck,
973+
// the refreshPackedRefs() need not be synchronized.
974+
synchronized (this) {
975+
if (packedRefsRefresher.get() != refresher) {
976+
refresher = packedRefsRefresher.get();
977+
if (refresher != null) {
978+
// Refresher now guaranteed to have been created after the
979+
// current thread entered getPackedRefsRefresher(), even if
980+
// it's currently out of date.
981+
return refresher;
982+
}
983+
}
984+
refresher = createPackedRefsRefresherAsLatest(curList);
985+
}
986+
return runAndClear(refresher);
987+
}
988+
989+
private boolean shouldRefreshPackedRefs(FileSnapshot snapshot) throws IOException {
953990
switch (trustPackedRefsStat) {
954991
case NEVER:
955992
break;
@@ -962,23 +999,34 @@ PackedRefList getPackedRefs() throws IOException {
962999
}
9631000
//$FALL-THROUGH$
9641001
case ALWAYS:
965-
if (!curList.snapshot.isModified(packedRefsFile)) {
966-
return curList;
1002+
if (!snapshot.isModified(packedRefsFile)) {
1003+
return false;
9671004
}
9681005
break;
9691006
case UNSET:
970-
if (trustFolderStat
971-
&& !curList.snapshot.isModified(packedRefsFile)) {
972-
return curList;
1007+
if (trustFolderStat && !snapshot.isModified(packedRefsFile)) {
1008+
return false;
9731009
}
9741010
break;
9751011
}
976-
977-
return refreshPackedRefs(curList);
1012+
return true;
9781013
}
9791014

9801015
PackedRefList refreshPackedRefs() throws IOException {
981-
return refreshPackedRefs(packedRefs.get());
1016+
return runAndClear(createPackedRefsRefresherAsLatest(packedRefs.get()))
1017+
.getOrThrowIOException();
1018+
}
1019+
1020+
private PackedRefsRefresher createPackedRefsRefresherAsLatest(PackedRefList curList) {
1021+
PackedRefsRefresher refresher = new PackedRefsRefresher(curList);
1022+
packedRefsRefresher.set(refresher);
1023+
return refresher;
1024+
}
1025+
1026+
private PackedRefsRefresher runAndClear(PackedRefsRefresher refresher) {
1027+
refresher.run();
1028+
packedRefsRefresher.compareAndSet(refresher, null);
1029+
return refresher;
9821030
}
9831031

9841032
private PackedRefList refreshPackedRefs(PackedRefList curList)
@@ -1002,7 +1050,7 @@ private PackedRefList readPackedRefs() throws IOException {
10021050
new DigestInputStream(
10031051
new FileInputStream(f), digest),
10041052
UTF_8))) {
1005-
return new PackedRefList(parsePackedRefs(br),
1053+
return new NonEmptyPackedRefList(parsePackedRefs(br),
10061054
snapshot,
10071055
ObjectId.fromRaw(digest.digest()));
10081056
}
@@ -1108,7 +1156,7 @@ protected void writeFile(String name, byte[] content)
11081156
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
11091157

11101158
byte[] digest = Constants.newMessageDigest().digest(content);
1111-
PackedRefList newPackedList = new PackedRefList(
1159+
PackedRefList newPackedList = new NonEmptyPackedRefList(
11121160
refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
11131161
packedRefs.compareAndSet(oldPackedList, newPackedList);
11141162
if (changed) {
@@ -1462,21 +1510,57 @@ static void sleep(long ms) throws InterruptedIOException {
14621510
}
14631511

14641512
static class PackedRefList extends RefList<Ref> {
1465-
1466-
private final FileSnapshot snapshot;
1467-
14681513
private final ObjectId id;
14691514

1470-
private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1515+
PackedRefList() {
1516+
this(RefList.emptyList(), ObjectId.zeroId());
1517+
}
1518+
1519+
protected PackedRefList(RefList<Ref> src, ObjectId id) {
14711520
super(src);
1521+
this.id = id;
1522+
}
1523+
1524+
public boolean shouldRefresh() throws IOException {
1525+
return true;
1526+
}
1527+
}
1528+
1529+
private static final PackedRefList NO_PACKED_REFS = new PackedRefList();
1530+
1531+
private class NonEmptyPackedRefList extends PackedRefList {
1532+
private final FileSnapshot snapshot;
1533+
1534+
private NonEmptyPackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId id) {
1535+
super(src, id);
14721536
snapshot = s;
1473-
id = i;
1537+
}
1538+
1539+
@Override
1540+
public boolean shouldRefresh() throws IOException {
1541+
return shouldRefreshPackedRefs(snapshot);
14741542
}
14751543
}
14761544

1477-
private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1478-
RefList.emptyList(), FileSnapshot.MISSING_FILE,
1479-
ObjectId.zeroId());
1545+
private class PackedRefsRefresher extends FutureTask<PackedRefList> {
1546+
private final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
1547+
1548+
public PackedRefsRefresher(PackedRefList curList) {
1549+
super(() -> refreshPackedRefs(curList));
1550+
}
1551+
1552+
public PackedRefList getOrThrowIOException() throws IOException {
1553+
try {
1554+
return get();
1555+
} catch (ExecutionException | InterruptedException e) {
1556+
throw new IOException(e);
1557+
}
1558+
}
1559+
1560+
public boolean shouldRefresh() throws IOException {
1561+
return shouldRefreshPackedRefs(snapshot);
1562+
}
1563+
}
14801564

14811565
private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
14821566
String name, String target) {

0 commit comments

Comments
 (0)