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 ;
@@ -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