Optionally create review notes asynchronously

Review notes are generally not vital to a server's operation and/or
may be created by one of several GitReferenceUpdatedListeners on the
blocking MergeOp path, so give administrators the option to run them
asynchronously.

Change-Id: I1f6c30f9e6fd38e8bd8574122d19d3a4d0b0dd18
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
index 976a5b1..42374ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
@@ -225,6 +225,9 @@
 
   private void createCodeReviewNote(Change change, PatchSet ps,
       HeaderFormatter fmt) throws OrmException, NoSuchChangeException {
+    // This races with the label normalization/writeback done by MergeOp. It may
+    // repeat some work, but results should be identical except in the case of
+    // an additional race with a permissions change.
     List<PatchSetApproval> approvals = labelNormalizer.normalize(
         change, reviewDb.patchSetApprovals().byPatchSet(ps.getId()).toList());
     PatchSetApproval submit = null;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/RefUpdateListener.java b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/RefUpdateListener.java
index 8dca4ad..7755776 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/RefUpdateListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/RefUpdateListener.java
@@ -18,6 +18,7 @@
 
 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.slf4j.Logger;
@@ -26,31 +27,77 @@
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ProjectRunnable;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.util.RequestScopePropagator;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 
 class RefUpdateListener implements GitReferenceUpdatedListener {
 
-  private static final Logger log =
-      LoggerFactory.getLogger(RefUpdateListener.class);
+  private static final Logger log = LoggerFactory
+      .getLogger(RefUpdateListener.class);
 
   private final CreateReviewNotes.Factory reviewNotesFactory;
   private final SchemaFactory<ReviewDb> schema;
   private final GitRepositoryManager repoManager;
+  private final WorkQueue workQueue;
+  private final RequestScopePropagator requestScopePropagator;
+  private final boolean async;
 
   @Inject
   RefUpdateListener(final CreateReviewNotes.Factory reviewNotesFactory,
       final SchemaFactory<ReviewDb> schema,
-      final GitRepositoryManager repoManager) {
+      final GitRepositoryManager repoManager, final WorkQueue workQueue,
+      final RequestScopePropagator requestScopePropagator,
+      @GerritServerConfig final Config config) {
     this.reviewNotesFactory = reviewNotesFactory;
     this.schema = schema;
     this.repoManager = repoManager;
+    this.workQueue = workQueue;
+    this.requestScopePropagator = requestScopePropagator;
+    this.async = config.getBoolean("reviewnotes", null, "async", false);
   }
 
   @Override
-  public void onGitReferenceUpdated(Event e) {
+  public void onGitReferenceUpdated(final Event e) {
+    if (async) {
+      workQueue.getDefaultQueue().submit(
+          requestScopePropagator.wrap(new ProjectRunnable() {
+            @Override
+            public void run() {
+              createReviewNotes(e);
+            }
+
+            @Override
+            public Project.NameKey getProjectNameKey() {
+              return new Project.NameKey(e.getProjectName());
+            }
+
+            @Override
+            public String getRemoteName() {
+              return null;
+            }
+
+            @Override
+            public boolean hasCustomizedPrint() {
+              return true;
+            }
+
+            @Override
+            public String toString() {
+              return "create-review-notes";
+            }
+          }));
+    } else {
+      createReviewNotes(e);
+    }
+  }
+
+  private void createReviewNotes(Event e) {
     Project.NameKey projectName = new Project.NameKey(e.getProjectName());
     Repository git;
     try {