4949import java .util .HashMap ;
5050import java .util .List ;
5151import java .util .Map ;
52+ import java .util .concurrent .ExecutionException ;
53+ import java .util .concurrent .FutureTask ;
5254import java .util .concurrent .atomic .AtomicInteger ;
5355import java .util .concurrent .atomic .AtomicReference ;
5456import 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