Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  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: I52fd7c9320ce57488ab54f8b5b24a8eabb002ea5
Forward-Compatible: checked
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 f49fbfd..4475465 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.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.cache.PerThreadCache;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.ReceiveCommitsExecutor;
@@ -134,7 +135,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 2b127b6..2de5dbc 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -3148,7 +3148,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;
@@ -3172,17 +3172,18 @@
                 }
 
                 for (String changeId : c.getFooterLines(CHANGE_ID)) {
-                  if (byKey == null) {
-                    byKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
+                  if (changeDataByKey == null) {
+                    changeDataByKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
                   }
 
-                  ChangeNotes onto = byKey.get(new Change.Key(changeId.trim()));
+                  ChangeData onto = changeDataByKey.get(new 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;
                   }
@@ -3252,11 +3253,14 @@
     }
   }
 
-  private Map<Change.Key, ChangeNotes> openChangesByKeyByBranch(Branch.NameKey branch) {
-    Map<Change.Key, ChangeNotes> r = new HashMap<>();
+  private Map<Change.Key, ChangeData> openChangesByKeyByBranch(Branch.NameKey branch) {
+    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
       }