Merge branch 'stable-3.10' into stable-3.11

* stable-3.10:
  Throw lockFailureException when RefDBLock occurs
  Add 3rd site in the local test environment
  Use fanout replication config for the local test environment
  Sort ref-names before locking them in filterAndLock

Change-Id: Ie85b9562c46a99722979174d66ba393d77d16618
diff --git a/setup_local_env/configs/replication.config b/setup_local_env/configs/replication.config
index ff0c248..7aa26fc 100644
--- a/setup_local_env/configs/replication.config
+++ b/setup_local_env/configs/replication.config
@@ -1,15 +1,3 @@
-[remote "$REPLICA_INSTANCE_ID"]
-    $PULL_REPLICATION_URL
-    $PULL_REPLICATION_API_URL
-    fetch = +refs/*:refs/*
-    mirror = true
-    timeout = 600
-    rescheduleDelay = 15
-    replicationDelay = $REPLICATION_DELAY_SEC
-    createMissingRepositories = true
-    replicateProjectDeletions = true
-    replicateHiddenProjects = true
-    threads = 5
 [gerrit]
     autoReload = true
     replicateOnStartup = false
diff --git a/setup_local_env/configs/replication.remote b/setup_local_env/configs/replication.remote
new file mode 100644
index 0000000..5724708
--- /dev/null
+++ b/setup_local_env/configs/replication.remote
@@ -0,0 +1,12 @@
+[remote]
+    $PULL_REPLICATION_URL
+    $PULL_REPLICATION_API_URL
+    fetch = +refs/*:refs/*
+    mirror = true
+    timeout = 600
+    rescheduleDelay = 15
+    replicationDelay = $REPLICATION_DELAY_SEC
+    createMissingRepositories = true
+    replicateProjectDeletions = true
+    replicateHiddenProjects = true
+    threads = 5
\ No newline at end of file
diff --git a/setup_local_env/haproxy-config/haproxy.cfg b/setup_local_env/haproxy-config/haproxy.cfg
index de4f709..eb8bce4 100644
--- a/setup_local_env/haproxy-config/haproxy.cfg
+++ b/setup_local_env/haproxy-config/haproxy.cfg
@@ -45,6 +45,7 @@
     http-check expect status 200
     server node1 $HA_GERRIT_SITE1_HOSTNAME:$HA_GERRIT_SITE1_HTTPD_PORT check inter 10s
     server node2 $HA_GERRIT_SITE2_HOSTNAME:$HA_GERRIT_SITE2_HTTPD_PORT check inter 10s backup
+    server node3 $HA_GERRIT_SITE3_HOSTNAME:$HA_GERRIT_SITE3_HTTPD_PORT check inter 10s backup
 
 backend write-backendnodes
     mode http
@@ -56,6 +57,7 @@
     http-check expect status 200
     server node1 $HA_GERRIT_SITE1_HOSTNAME:$HA_GERRIT_SITE1_HTTPD_PORT check inter 10s
     server node2 $HA_GERRIT_SITE2_HOSTNAME:$HA_GERRIT_SITE2_HTTPD_PORT check inter 10s backup
+    server node3 $HA_GERRIT_SITE3_HOSTNAME:$HA_GERRIT_SITE3_HTTPD_PORT check inter 10s backup
 
 backend ssh
     mode tcp
@@ -66,4 +68,5 @@
     timeout server 5m
     server ssh_node1 $HA_GERRIT_SITE1_HOSTNAME:$HA_GERRIT_SITE1_SSHD_PORT check inter 10s check port $HA_GERRIT_SITE1_HTTPD_PORT inter 10s
     server ssh_node2 $HA_GERRIT_SITE2_HOSTNAME:$HA_GERRIT_SITE2_SSHD_PORT check inter 10s check port $HA_GERRIT_SITE2_HTTPD_PORT inter 10s backup
+    server ssh_node3 $HA_GERRIT_SITE3_HOSTNAME:$HA_GERRIT_SITE3_SSHD_PORT check inter 10s check port $HA_GERRIT_SITE2_HTTPD_PORT inter 10s backup
 
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index f56ef90..bab281a 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -78,6 +78,23 @@
   done
 }
 
+function copy_replication_remote_file {
+  fanout_template_file=$SCRIPT_DIR/configs/replication.remote
+
+  CONFIG_TEST_SITE=$1
+  export REPLICATION_HTTPD_PORT=$2
+  export REPLICATION_HOSTNAME=$3
+  export PULL_REPLICATION_URL=$(get_pull_replication_url $REPLICATION_HOSTNAME)
+  export PULL_REPLICATION_API_URL=$(get_pull_replication_api_url $REPLICATION_HOSTNAME)
+  export REPLICA_INSTANCE_ID=$4
+  fanout_file=$REPLICA_INSTANCE_ID.config
+
+  mkdir -p $CONFIG_TEST_SITE/replication
+
+  echo "Replacing variables for file $fanout_template_file and copying to $CONFIG_TEST_SITE/replication/$fanout_file"
+  cat $fanout_template_file | envsubst | sed 's/#{name}#/${name}/g' > $CONFIG_TEST_SITE/replication/$fanout_file
+}
+
 function start_ha_proxy {
 
   export HA_GERRIT_CANONICAL_HOSTNAME=$GERRIT_CANONICAL_HOSTNAME
@@ -87,11 +104,14 @@
 
   export HA_GERRIT_SITE1_HOSTNAME=$GERRIT_1_HOSTNAME
   export HA_GERRIT_SITE2_HOSTNAME=$GERRIT_2_HOSTNAME
+  export HA_GERRIT_SITE3_HOSTNAME=$GERRIT_3_HOSTNAME
   export HA_GERRIT_SITE1_HTTPD_PORT=$GERRIT_1_HTTPD_PORT
   export HA_GERRIT_SITE2_HTTPD_PORT=$GERRIT_2_HTTPD_PORT
+  export HA_GERRIT_SITE3_HTTPD_PORT=$GERRIT_3_HTTPD_PORT
 
   export HA_GERRIT_SITE1_SSHD_PORT=$GERRIT_1_SSHD_PORT
   export HA_GERRIT_SITE2_SSHD_PORT=$GERRIT_2_SSHD_PORT
+  export HA_GERRIT_SITE3_SSHD_PORT=$GERRIT_3_SSHD_PORT
 
   cat $SCRIPT_DIR/haproxy-config/haproxy.cfg | envsubst > $HA_PROXY_CONFIG_DIR/haproxy.cfg
 
@@ -133,13 +153,28 @@
   CONFIG_TEST_SITE_2=$LOCATION_TEST_SITE_2/etc
   GERRIT_SITE2_REMOTE_DEBUG_PORT="5006"
   GERRIT_SITE2_INSTANCE_ID="instance-2"
+  # SITE 3
+  GERRIT_SITE3_HOSTNAME=$7
+  GERRIT_SITE3_HTTPD_PORT=$8
+  GERRIT_SITE3_SSHD_PORT=$9
+  CONFIG_TEST_SITE_3=$LOCATION_TEST_SITE_3/etc
+  GERRIT_SITE3_REMOTE_DEBUG_PORT="5007"
+  GERRIT_SITE3_INSTANCE_ID="instance-3"
 
   # Set config SITE1
   copy_config_files $CONFIG_TEST_SITE_1 $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_SSHD_PORT $GERRIT_SITE2_HTTPD_PORT $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE1_REMOTE_DEBUG_PORT $GERRIT_SITE1_INSTANCE_ID $GERRIT_SITE2_INSTANCE_ID
-
+  copy_replication_remote_file $CONFIG_TEST_SITE_1 $GERRIT_SITE2_HTTPD_PORT $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_INSTANCE_ID
+  copy_replication_remote_file $CONFIG_TEST_SITE_1 $GERRIT_SITE3_HTTPD_PORT $GERRIT_SITE3_HOSTNAME $GERRIT_SITE3_INSTANCE_ID
 
   # Set config SITE2
   copy_config_files $CONFIG_TEST_SITE_2 $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE2_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_REMOTE_DEBUG_PORT $GERRIT_SITE2_INSTANCE_ID $GERRIT_SITE1_INSTANCE_ID
+  copy_replication_remote_file $CONFIG_TEST_SITE_2 $GERRIT_SITE1_HTTPD_PORT $GERRIT_SITE1_HOSTNAME $GERRIT_SITE1_INSTANCE_ID
+  copy_replication_remote_file $CONFIG_TEST_SITE_2 $GERRIT_SITE3_HTTPD_PORT $GERRIT_SITE3_HOSTNAME $GERRIT_SITE3_INSTANCE_ID
+
+  # Set config SITE3
+  copy_config_files $CONFIG_TEST_SITE_3 $GERRIT_SITE3_HTTPD_PORT $LOCATION_TEST_SITE_3 $GERRIT_SITE3_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $GERRIT_SITE1_HOSTNAME $GERRIT_SITE3_HOSTNAME $GERRIT_SITE3_REMOTE_DEBUG_PORT $GERRIT_SITE3_INSTANCE_ID $GERRIT_SITE1_INSTANCE_ID
+  copy_replication_remote_file $CONFIG_TEST_SITE_3 $GERRIT_SITE1_HTTPD_PORT $GERRIT_SITE1_HOSTNAME $GERRIT_SITE1_INSTANCE_ID
+  copy_replication_remote_file $CONFIG_TEST_SITE_3 $GERRIT_SITE2_HTTPD_PORT $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_INSTANCE_ID
 }
 
 function is_docker_desktop {
@@ -406,10 +441,13 @@
 export GERRIT_CANONICAL_PORT=${GERRIT_CANONICAL_PORT:-"8080"}
 GERRIT_1_HOSTNAME=${GERRIT_1_HOSTNAME:-"localhost"}
 GERRIT_2_HOSTNAME=${GERRIT_2_HOSTNAME:-"localhost"}
+GERRIT_3_HOSTNAME=${GERRIT_3_HOSTNAME:-"localhost"}
 GERRIT_1_HTTPD_PORT=${GERRIT_1_HTTPD_PORT:-"18080"}
 GERRIT_2_HTTPD_PORT=${GERRIT_2_HTTPD_PORT:-"18081"}
+GERRIT_3_HTTPD_PORT=${GERRIT_3_HTTPD_PORT:-"18082"}
 GERRIT_1_SSHD_PORT=${GERRIT_1_SSHD_PORT:-"39418"}
 GERRIT_2_SSHD_PORT=${GERRIT_2_SSHD_PORT:-"49418"}
+GERRIT_3_SSHD_PORT=${GERRIT_3_SSHD_PORT:-"59418"}
 export REPLICATION_DELAY_SEC=${REPLICATION_DELAY_SEC:-"5"}
 export SSH_ADVERTISED_PORT=${SSH_ADVERTISED_PORT:-"29418"}
 HTTPS_ENABLED=${HTTPS_ENABLED:-"false"}
@@ -418,6 +456,7 @@
 export COMMON_LOCATION=$DEPLOYMENT_LOCATION/gerrit_setup
 LOCATION_TEST_SITE_1=$COMMON_LOCATION/instance-1
 LOCATION_TEST_SITE_2=$COMMON_LOCATION/instance-2
+LOCATION_TEST_SITE_3=$COMMON_LOCATION/instance-3
 HA_PROXY_CONFIG_DIR=$COMMON_LOCATION/ha-proxy-config
 HA_PROXY_CERTIFICATES_DIR="$HA_PROXY_CONFIG_DIR/certificates"
 PROMETHEUS_CONFIG_DIR=$COMMON_LOCATION/prometheus-config
@@ -430,7 +469,7 @@
 export FAKE_NFS=$COMMON_LOCATION/fake_nfs
 
 if [ "$JUST_CLEANUP_ENV" = "true" ];then
-  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
+  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $LOCATION_TEST_SITE_3 $COMMON_LOCATION
   exit 0
 fi
 
@@ -500,10 +539,10 @@
 # New installation
 if [ $NEW_INSTALLATION = "true" ]; then
 
-  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
+  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $LOCATION_TEST_SITE_3 $COMMON_LOCATION
 
   echo "Setting up directories"
-  mkdir -p $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $HA_PROXY_CERTIFICATES_DIR $FAKE_NFS
+  mkdir -p $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $LOCATION_TEST_SITE_3 $HA_PROXY_CERTIFICATES_DIR $FAKE_NFS
   java -jar $DEPLOYMENT_LOCATION/gerrit.war init --batch --no-auto-start --install-all-plugins --dev -d $LOCATION_TEST_SITE_1
 
   # Deploying TLS certificates
@@ -544,18 +583,19 @@
   # Replicating environment
   echo "Replicating environment"
   cp -fR $LOCATION_TEST_SITE_1/* $LOCATION_TEST_SITE_2
+  cp -fR $LOCATION_TEST_SITE_1/* $LOCATION_TEST_SITE_3
 
-  echo "Link pullreplication plugin"
-  ln -s $LOCATION_TEST_SITE_1/plugins/pull-replication.jar $LOCATION_TEST_SITE_1/lib/pull-replication.jar
-  ln -s $LOCATION_TEST_SITE_2/plugins/pull-replication.jar $LOCATION_TEST_SITE_2/lib/pull-replication.jar
+  for location_test_site in $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $LOCATION_TEST_SITE_3
+  do
+    echo "Link pullreplication plugin on $location_test_site"
+    ln -s $location_test_site/plugins/pull-replication.jar $location_test_site/lib/pull-replication.jar
 
-  echo "Link multi-site library to plugin directory"
-  ln -s $LOCATION_TEST_SITE_1/lib/multi-site.jar $LOCATION_TEST_SITE_1/plugins/multi-site.jar
-  ln -s $LOCATION_TEST_SITE_2/lib/multi-site.jar $LOCATION_TEST_SITE_2/plugins/multi-site.jar
+    echo "Link multi-site library to plugin directory on $location_test_site"
+    ln -s $location_test_site/lib/multi-site.jar $location_test_site/plugins/multi-site.jar
 
-  echo "Copy pull-replication plugin"
-  cp -f $DEPLOYMENT_LOCATION/pull-replication.jar $LOCATION_TEST_SITE_1/plugins/pull-replication.jar
-  cp -f $DEPLOYMENT_LOCATION/pull-replication.jar $LOCATION_TEST_SITE_2/plugins/pull-replication.jar
+    echo "Copy pull-replication plugin on $location_test_site"
+    cp -f $DEPLOYMENT_LOCATION/pull-replication.jar $location_test_site/plugins/pull-replication.jar
+  done
 
 fi
 
@@ -577,16 +617,15 @@
 prepare_broker_data
 
 echo "Re-deploying configuration files"
-deploy_config_files $GERRIT_1_HOSTNAME $GERRIT_1_HTTPD_PORT $GERRIT_1_SSHD_PORT $GERRIT_2_HOSTNAME $GERRIT_2_HTTPD_PORT $GERRIT_2_SSHD_PORT
-echo "Move replication and delete-project plugins to /lib on gerrit site 1"
-mv $LOCATION_TEST_SITE_1/plugins/{replication,delete-project}.jar $LOCATION_TEST_SITE_1/lib/.
-echo "Starting gerrit site 1"
-$LOCATION_TEST_SITE_1/bin/gerrit.sh restart
-echo "Move replication plugin to /lib on gerrit site 2"
-mv $LOCATION_TEST_SITE_2/plugins/{replication,delete-project}.jar $LOCATION_TEST_SITE_2/lib/.
-echo "Starting gerrit site 2"
-$LOCATION_TEST_SITE_2/bin/gerrit.sh restart
+deploy_config_files $GERRIT_1_HOSTNAME $GERRIT_1_HTTPD_PORT $GERRIT_1_SSHD_PORT $GERRIT_2_HOSTNAME $GERRIT_2_HTTPD_PORT $GERRIT_2_SSHD_PORT $GERRIT_3_HOSTNAME $GERRIT_3_HTTPD_PORT $GERRIT_3_SSHD_PORT
 
+for location_test_site in $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $LOCATION_TEST_SITE_3
+do
+  echo "Move replication and delete-project plugins to /lib on gerrit $location_test_site"
+  mv $location_test_site/plugins/{replication,delete-project}.jar $location_test_site/lib/.
+  echo "Starting gerrit $location_test_site"
+  $location_test_site/bin/gerrit.sh restart
+done
 
 if [[ $(ps -ax | grep haproxy | grep "gerrit_setup/ha-proxy-config" | awk '{print $1}' | wc -l) -lt 1 ]];then
   echo "Starting haproxy"
@@ -604,10 +643,12 @@
 echo "GERRIT HA-PROXY: $GERRIT_CANONICAL_WEB_URL"
 echo "GERRIT-1: http://$GERRIT_1_HOSTNAME:$GERRIT_1_HTTPD_PORT"
 echo "GERRIT-2: http://$GERRIT_2_HOSTNAME:$GERRIT_2_HTTPD_PORT"
+echo "GERRIT-3: http://$GERRIT_3_HOSTNAME:$GERRIT_3_HTTPD_PORT"
 echo "Prometheus: http://localhost:9090"
 echo
 echo "Site-1: $LOCATION_TEST_SITE_1"
 echo "Site-2: $LOCATION_TEST_SITE_2"
+echo "Site-3: $LOCATION_TEST_SITE_3"
 echo
 echo "$HTTPS_CLONE_MSG"
 echo
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
index 89310bf..8423d2e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationFetchFilter.java
@@ -19,6 +19,7 @@
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
 import com.gerritforge.gerrit.globalrefdb.RefDbLockException;
 import com.gerritforge.gerrit.globalrefdb.validation.SharedRefDatabaseWrapper;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
@@ -26,6 +27,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.replication.pull.FetchOne.LockFailureException;
 import com.googlesource.gerrit.plugins.replication.pull.ReplicationFetchFilter;
 import java.io.IOException;
 import java.util.Collections;
@@ -34,6 +36,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -96,20 +99,25 @@
 
   @Override
   public Map<String, AutoCloseable> filterAndLock(String projectName, Set<String> fetchRefs)
-      throws RefDbLockException {
+      throws LockFailureException {
+    // Use a SortedSet to ensure that they are always locked in the same order and therefore
+    // prevents potential deadlocks where multiple fetch tasks are mutually locking each other
+    // by locking different refs in different orders.
+    SortedSet<String> sortedFetchRefs = ImmutableSortedSet.copyOf(fetchRefs);
     Project.NameKey projectKey = Project.nameKey(projectName);
     Set<String> filteredRefs = new HashSet<>();
     Map<String, AutoCloseable> refLocks = new HashMap<>();
     try {
-      for (String ref : fetchRefs) {
+      for (String ref : sortedFetchRefs) {
         refLocks.put(ref, sharedRefDb.lockLocalRef(projectKey, ref));
       }
-      filteredRefs.addAll(filter(projectName, fetchRefs));
+      filteredRefs.addAll(filter(projectName, sortedFetchRefs));
     } catch (RefDbLockException lockException) {
       filteredRefs.clear();
-      throw lockException;
+      throw new LockFailureException(
+          "Unable to lock refs " + sortedFetchRefs + " for project " + projectName, lockException);
     } finally {
-      for (String excludedRef : Sets.difference(fetchRefs, filteredRefs)) {
+      for (String excludedRef : Sets.difference(sortedFetchRefs, filteredRefs)) {
         AutoCloseable excludedLock = refLocks.remove(excludedRef);
         if (excludedLock != null) {
           try {