Merge branch 'stable-3.0' into stable-3.1

* stable-3.0:
  Allow async receive-commits to have a thread-local cache
  Fix RepoRefCache stale checks during NoteDb rebuild
  Lazy load change notes when submit by push

Change-Id: Ie82487167fc84ec543083b018c3fde1b0a041e1d
Release-Notes: skip
diff --git a/java/com/google/gerrit/server/git/RepoRefCache.java b/java/com/google/gerrit/server/git/RepoRefCache.java
index c813b83..4fa3311 100644
--- a/java/com/google/gerrit/server/git/RepoRefCache.java
+++ b/java/com/google/gerrit/server/git/RepoRefCache.java
@@ -136,8 +136,9 @@
     }
 
     try {
-      ObjectId diskId = refdb.exactRef(refName).getObjectId();
-      boolean isStale = !Optional.ofNullable(diskId).equals(id);
+      Optional<ObjectId> diskId =
+          Optional.ofNullable(refdb.exactRef(refName)).map(Ref::getObjectId);
+      boolean isStale = !diskId.equals(id);
       if (isStale) {
         log.atSevere().log(
             "Repository "
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index f5c3aa9..c68ed30 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.cache.PerThreadCache;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -133,7 +134,7 @@
     public void run() {
       String oldName = Thread.currentThread().getName();
       Thread.currentThread().setName(oldName + "-for-" + name);
-      try {
+      try (PerThreadCache threadLocalCache = PerThreadCache.create(null)) {
         receiveCommits.processCommands(commands, progress);
       } finally {
         Thread.currentThread().setName(oldName);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 3b5b080..012ba43 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -3278,7 +3278,7 @@
                 }
 
                 ListMultimap<ObjectId, Ref> byCommit = changeRefsById();
-                Map<Change.Key, ChangeNotes> byKey = null;
+                Map<Change.Key, ChangeData> changeDataByKey = null;
                 List<ReplaceRequest> replaceAndClose = new ArrayList<>();
 
                 int existingPatchSets = 0;
@@ -3302,17 +3302,19 @@
                   }
 
                   for (String changeId : c.getFooterLines(FooterConstants.CHANGE_ID)) {
-                    if (byKey == null) {
-                      byKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
+                    if (changeDataByKey == null) {
+                      changeDataByKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
                     }
 
-                    ChangeNotes onto = byKey.get(Change.key(changeId.trim()));
+                    ChangeData onto = changeDataByKey.get(Change.key(changeId.trim()));
                     if (onto != null) {
                       newPatchSets++;
                       // Hold onto this until we're done with the walk, as the call to
                       // req.validate below calls isMergedInto which resets the walk.
-                      ReplaceRequest req = new ReplaceRequest(onto.getChangeId(), c, cmd, false);
-                      req.notes = onto;
+                      ChangeNotes ontoNotes = onto.notes();
+                      ReplaceRequest req =
+                          new ReplaceRequest(ontoNotes.getChangeId(), c, cmd, false);
+                      req.notes = ontoNotes;
                       replaceAndClose.add(req);
                       continue COMMIT;
                     }
@@ -3384,13 +3386,16 @@
     }
   }
 
-  private Map<Change.Key, ChangeNotes> openChangesByKeyByBranch(BranchNameKey branch) {
+  private Map<Change.Key, ChangeData> openChangesByKeyByBranch(BranchNameKey branch) {
     try (TraceTimer traceTimer =
         newTimer("openChangesByKeyByBranch", Metadata.builder().branchName(branch.branch()))) {
-      Map<Change.Key, ChangeNotes> r = new HashMap<>();
+      Map<Change.Key, ChangeData> r = new HashMap<>();
       for (ChangeData cd : queryProvider.get().byBranchOpen(branch)) {
         try {
-          r.put(cd.change().getKey(), cd.notes());
+          // ChangeData is not materialised into a ChangeNotes for avoiding
+          // to load a potentially large number of changes meta-data into memory
+          // which would cause unnecessary disk I/O, CPU and heap utilisation.
+          r.put(cd.change().getKey(), cd);
         } catch (NoSuchChangeException e) {
           // Ignore deleted change
         }