Skip to content

Commit 274d177

Browse files
committed
Merge branch 'stable-7.0' into stable-7.1
* stable-7.0: Avoid reading packed-refs concurrently sometimes Improve flaky InterruptTimerTests Change-Id: Icd62117b74eda95474e804ddb665820cf012ee80
2 parents 5dd48bc + 02c8c75 commit 274d177

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;
@@ -150,6 +152,9 @@ public class RefDirectory extends RefDatabase {
150152
/** Immutable sorted list of packed references. */
151153
final AtomicReference<PackedRefList> packedRefs = new AtomicReference<>();
152154

155+
private final AtomicReference<PackedRefsRefresher> packedRefsRefresher =
156+
new AtomicReference<>();
157+
153158
/**
154159
* Lock for coordinating operations within a single process that may contend
155160
* on the {@code packed-refs} file.
@@ -977,8 +982,40 @@ else if (0 <= (idx = packed.find(dst.getName())))
977982
}
978983

979984
PackedRefList getPackedRefs() throws IOException {
980-
final PackedRefList curList = packedRefs.get();
985+
PackedRefList curList = packedRefs.get();
986+
if (!curList.shouldRefresh()) {
987+
return curList;
988+
}
989+
return getPackedRefsRefresher(curList).getOrThrowIOException();
990+
}
981991

992+
private PackedRefsRefresher getPackedRefsRefresher(PackedRefList curList)
993+
throws IOException {
994+
PackedRefsRefresher refresher = packedRefsRefresher.get();
995+
if (refresher != null && !refresher.shouldRefresh()) {
996+
return refresher;
997+
}
998+
// This synchronized is NOT needed for correctness. Instead it is used
999+
// as a throttling mechanism to ensure that only one "read" thread does
1000+
// the work to refresh the file. In order to avoid stalling writes which
1001+
// must already be serialized and tend to be a bottleneck,
1002+
// the refreshPackedRefs() need not be synchronized.
1003+
synchronized (this) {
1004+
if (packedRefsRefresher.get() != refresher) {
1005+
refresher = packedRefsRefresher.get();
1006+
if (refresher != null) {
1007+
// Refresher now guaranteed to have been created after the
1008+
// current thread entered getPackedRefsRefresher(), even if
1009+
// it's currently out of date.
1010+
return refresher;
1011+
}
1012+
}
1013+
refresher = createPackedRefsRefresherAsLatest(curList);
1014+
}
1015+
return runAndClear(refresher);
1016+
}
1017+
1018+
private boolean shouldRefreshPackedRefs(FileSnapshot snapshot) throws IOException {
9821019
switch (trustPackedRefsStat) {
9831020
case NEVER:
9841021
break;
@@ -991,23 +1028,34 @@ PackedRefList getPackedRefs() throws IOException {
9911028
}
9921029
//$FALL-THROUGH$
9931030
case ALWAYS:
994-
if (!curList.snapshot.isModified(packedRefsFile)) {
995-
return curList;
1031+
if (!snapshot.isModified(packedRefsFile)) {
1032+
return false;
9961033
}
9971034
break;
9981035
case UNSET:
999-
if (trustFolderStat
1000-
&& !curList.snapshot.isModified(packedRefsFile)) {
1001-
return curList;
1036+
if (trustFolderStat && !snapshot.isModified(packedRefsFile)) {
1037+
return false;
10021038
}
10031039
break;
10041040
}
1005-
1006-
return refreshPackedRefs(curList);
1041+
return true;
10071042
}
10081043

10091044
PackedRefList refreshPackedRefs() throws IOException {
1010-
return refreshPackedRefs(packedRefs.get());
1045+
return runAndClear(createPackedRefsRefresherAsLatest(packedRefs.get()))
1046+
.getOrThrowIOException();
1047+
}
1048+
1049+
private PackedRefsRefresher createPackedRefsRefresherAsLatest(PackedRefList curList) {
1050+
PackedRefsRefresher refresher = new PackedRefsRefresher(curList);
1051+
packedRefsRefresher.set(refresher);
1052+
return refresher;
1053+
}
1054+
1055+
private PackedRefsRefresher runAndClear(PackedRefsRefresher refresher) {
1056+
refresher.run();
1057+
packedRefsRefresher.compareAndSet(refresher, null);
1058+
return refresher;
10111059
}
10121060

10131061
private PackedRefList refreshPackedRefs(PackedRefList curList)
@@ -1031,7 +1079,7 @@ private PackedRefList readPackedRefs() throws IOException {
10311079
new DigestInputStream(
10321080
new FileInputStream(f), digest),
10331081
UTF_8))) {
1034-
return new PackedRefList(parsePackedRefs(br),
1082+
return new NonEmptyPackedRefList(parsePackedRefs(br),
10351083
snapshot,
10361084
ObjectId.fromRaw(digest.digest()));
10371085
}
@@ -1137,7 +1185,7 @@ protected void writeFile(String name, byte[] content)
11371185
throw new ObjectWritingException(MessageFormat.format(JGitText.get().unableToWrite, name));
11381186

11391187
byte[] digest = Constants.newMessageDigest().digest(content);
1140-
PackedRefList newPackedList = new PackedRefList(
1188+
PackedRefList newPackedList = new NonEmptyPackedRefList(
11411189
refs, lck.getCommitSnapshot(), ObjectId.fromRaw(digest));
11421190
packedRefs.compareAndSet(oldPackedList, newPackedList);
11431191
if (changed) {
@@ -1491,21 +1539,57 @@ static void sleep(long ms) throws InterruptedIOException {
14911539
}
14921540

14931541
static class PackedRefList extends RefList<Ref> {
1494-
1495-
private final FileSnapshot snapshot;
1496-
14971542
private final ObjectId id;
14981543

1499-
private PackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId i) {
1544+
PackedRefList() {
1545+
this(RefList.emptyList(), ObjectId.zeroId());
1546+
}
1547+
1548+
protected PackedRefList(RefList<Ref> src, ObjectId id) {
15001549
super(src);
1550+
this.id = id;
1551+
}
1552+
1553+
public boolean shouldRefresh() throws IOException {
1554+
return true;
1555+
}
1556+
}
1557+
1558+
private static final PackedRefList NO_PACKED_REFS = new PackedRefList();
1559+
1560+
private class NonEmptyPackedRefList extends PackedRefList {
1561+
private final FileSnapshot snapshot;
1562+
1563+
private NonEmptyPackedRefList(RefList<Ref> src, FileSnapshot s, ObjectId id) {
1564+
super(src, id);
15011565
snapshot = s;
1502-
id = i;
1566+
}
1567+
1568+
@Override
1569+
public boolean shouldRefresh() throws IOException {
1570+
return shouldRefreshPackedRefs(snapshot);
15031571
}
15041572
}
15051573

1506-
private static final PackedRefList NO_PACKED_REFS = new PackedRefList(
1507-
RefList.emptyList(), FileSnapshot.MISSING_FILE,
1508-
ObjectId.zeroId());
1574+
private class PackedRefsRefresher extends FutureTask<PackedRefList> {
1575+
private final FileSnapshot snapshot = FileSnapshot.save(packedRefsFile);
1576+
1577+
public PackedRefsRefresher(PackedRefList curList) {
1578+
super(() -> refreshPackedRefs(curList));
1579+
}
1580+
1581+
public PackedRefList getOrThrowIOException() throws IOException {
1582+
try {
1583+
return get();
1584+
} catch (ExecutionException | InterruptedException e) {
1585+
throw new IOException(e);
1586+
}
1587+
}
1588+
1589+
public boolean shouldRefresh() throws IOException {
1590+
return shouldRefreshPackedRefs(snapshot);
1591+
}
1592+
}
15091593

15101594
private static LooseSymbolicRef newSymbolicRef(FileSnapshot snapshot,
15111595
String name, String target) {

0 commit comments

Comments
 (0)