Merge branch 'stable-3.9'

* stable-3.9:
  Fix ApplyObjectActionIT flakiness due to the wrong ref used
  Force async fetch as a fallback for sync replication
  Index change asynchronously upon refs replicated event
  Address follow-up comments to Change 396868
  Introduce wait for replication events to reduce flaky tests
  Fail apply-object on change /meta when missing patch-set

Change-Id: Ib0783cb2b0863f7327b415ee9917956e1cc5f1a1
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index 2eb26e8..baeb330 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -774,7 +774,7 @@
   private void fireBeforeStartupEvents() {
     Set<String> eventsReplayed = new HashSet<>();
     ReferenceBatchUpdatedEvent event;
-    while ((event = beforeStartupEventsQueue.poll()) != null) {
+    while ((event = beforeStartupEventsQueue.peek()) != null) {
       String eventKey =
           String.format(
               "%s:%s",
@@ -787,6 +787,7 @@
         fire(event);
         eventsReplayed.add(eventKey);
       }
+      beforeStartupEventsQueue.remove(event);
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index 6c24cbc..a69afa4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -27,12 +27,15 @@
 import com.google.common.net.MediaType;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.index.project.ProjectIndexer;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -70,6 +73,7 @@
   private final ProjectIndexer projectIndexer;
   private final ApplyObjectCommand applyObjectCommand;
   private final ProjectCache projectCache;
+  private final DynamicSet<NewProjectCreatedListener> newProjectCreatedListeners;
 
   @Inject
   ProjectInitializationAction(
@@ -78,13 +82,15 @@
       PermissionBackend permissionBackend,
       ProjectIndexer projectIndexer,
       ApplyObjectCommand applyObjectCommand,
-      ProjectCache projectCache) {
+      ProjectCache projectCache,
+      DynamicSet<NewProjectCreatedListener> newProjectCreatedListeners) {
     this.gerritConfigOps = gerritConfigOps;
     this.userProvider = userProvider;
     this.permissionBackend = permissionBackend;
     this.projectIndexer = projectIndexer;
     this.applyObjectCommand = applyObjectCommand;
     this.projectCache = projectCache;
+    this.newProjectCreatedListeners = newProjectCreatedListeners;
   }
 
   @Override
@@ -179,6 +185,10 @@
       throw new BadRequestException("Configuration data cannot contain change meta refs", e);
     }
     projectCache.onCreateProject(Project.nameKey(projectName));
+    // In case pull-replication is used in conjunction with multi-site, by convention the remote
+    // label is populated with the instanceId. That's why we are passing input.getLabel()
+    // to the Event to notify
+    notifyListenersOfNewProjectCreation(projectName, input.getLabel());
     repLog.info(
         "Init project API from {} for {}:{} - {}",
         input.getLabel(),
@@ -246,4 +256,38 @@
       return false;
     }
   }
+
+  private void notifyListenersOfNewProjectCreation(String projectName, String instanceId) {
+    NewProjectCreatedListener.Event newProjectCreatedEvent =
+        new Event(RefNames.REFS_CONFIG, projectName, instanceId);
+    newProjectCreatedListeners.forEach(l -> l.onNewProjectCreated(newProjectCreatedEvent));
+  }
+
+  private static class Event extends AbstractNoNotifyEvent
+      implements NewProjectCreatedListener.Event {
+    private final String headName;
+    private final String projectName;
+    private final String instanceId;
+
+    public Event(String headName, String projectName, String maybeInstanceId) {
+      this.headName = headName;
+      this.projectName = projectName;
+      this.instanceId = maybeInstanceId;
+    }
+
+    @Override
+    public String getHeadName() {
+      return headName;
+    }
+
+    @Override
+    public String getProjectName() {
+      return projectName;
+    }
+
+    @Override
+    public String getInstanceId() {
+      return instanceId;
+    }
+  }
 }