Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Add batchThreadPoolSize to high-availability.config
  Use dedicated Executor Service for batch indexing

Change-Id: Iac5a645e367684245a0cb406b33d5b6d39efe494
diff --git a/.bazelversion b/.bazelversion
index fd2a018..7c69a55 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.1.0
+3.7.0
diff --git a/BUILD b/BUILD
index 577b49d..c3f5a11 100644
--- a/BUILD
+++ b/BUILD
@@ -41,7 +41,6 @@
     visibility = ["//visibility:public"],
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":high-availability__plugin",
-        "@mockito//jar",
         "@wiremock//jar",
     ],
 )
diff --git a/WORKSPACE b/WORKSPACE
index 456e679..666572c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,26 +3,15 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "6c39deb06f58bb62162ccb6865964f531739f512",
+    commit = "a029d8e41d6211c8b23052aa0a0c2c7649577e85",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
-# Snapshot Plugin API
-#load(
-#    "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
-#    "gerrit_api_maven_local",
-#)
-
-# Load snapshot Plugin API
-#gerrit_api_maven_local()
-
-# Release Plugin API
 load(
     "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
     "gerrit_api",
 )
 
-# Load release Plugin API
 gerrit_api()
 
 load("//:external_plugin_deps.bzl", "external_plugin_deps")
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 12bf919..0f8b43b 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,39 +3,8 @@
 def external_plugin_deps():
     maven_jar(
         name = "wiremock",
-        artifact = "com.github.tomakehurst:wiremock-standalone:2.26.3",
-        sha1 = "245c6efae2cbcb4e4f3457caf3d1c030cbaf2eb5",
-    )
-
-    maven_jar(
-        name = "mockito",
-        artifact = "org.mockito:mockito-core:2.28.2",
-        sha1 = "91110215a8cb9b77a46e045ee758f77d79167cc0",
-        deps = [
-            "@byte-buddy//jar",
-            "@byte-buddy-agent//jar",
-            "@objenesis//jar",
-        ],
-    )
-
-    BYTE_BUDDY_VERSION = "1.9.10"
-
-    maven_jar(
-        name = "byte-buddy",
-        artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
-        sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840",
-    )
-
-    maven_jar(
-        name = "byte-buddy-agent",
-        artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
-        sha1 = "9674aba5ee793e54b864952b001166848da0f26b",
-    )
-
-    maven_jar(
-        name = "objenesis",
-        artifact = "org.objenesis:objenesis:2.6",
-        sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+        artifact = "com.github.tomakehurst:wiremock-standalone:2.27.2",
+        sha1 = "327647a19b2319af2526b9c33a5733a2241723e0",
     )
 
     maven_jar(
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index 99cc106..093628e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -45,6 +45,9 @@
 public class Configuration {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
+  public static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
+  public static final int DEFAULT_TIMEOUT_MS = 5000;
+
   // common parameter to peerInfo section
   static final String PEER_INFO_SECTION = "peerInfo";
 
@@ -55,7 +58,6 @@
   static final int DEFAULT_INDEX_RETRY_INTERVAL = 30000;
   static final int DEFAULT_THREAD_POOL_SIZE = 4;
   static final String NUM_STRIPED_LOCKS = "numStripedLocks";
-  static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
 
   private final Main main;
   private final AutoReindex autoReindex;
@@ -334,6 +336,9 @@
   }
 
   public static class Http {
+    public static final int DEFAULT_MAX_TRIES = 360;
+    public static final int DEFAULT_RETRY_INTERVAL = 10000;
+
     static final String HTTP_SECTION = "http";
     static final String USER_KEY = "user";
     static final String PASSWORD_KEY = "password";
@@ -342,10 +347,6 @@
     static final String MAX_TRIES_KEY = "maxTries";
     static final String RETRY_INTERVAL_KEY = "retryInterval";
 
-    static final int DEFAULT_TIMEOUT_MS = 5000;
-    static final int DEFAULT_MAX_TRIES = 360;
-    static final int DEFAULT_RETRY_INTERVAL = 10000;
-
     private final String user;
     private final String password;
     private final int connectionTimeout;
@@ -447,6 +448,7 @@
   public static class Index extends Forwarding {
     static final String INDEX_SECTION = "index";
     static final String MAX_TRIES_KEY = "maxTries";
+    static final String WAIT_TIMEOUT_KEY = "waitTimeout";
     static final String RETRY_INTERVAL_KEY = "retryInterval";
     static final String SYNCHRONIZE_FORCED_KEY = "synchronizeForced";
     static final boolean DEFAULT_SYNCHRONIZE_FORCED = true;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
index 6635b80..8a267d6 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
@@ -24,6 +24,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.CACHE_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY;
@@ -33,7 +34,6 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
index de156a2..eb65106 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
@@ -19,11 +19,9 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.sql.Timestamp;
@@ -48,12 +46,12 @@
   }
 
   @Override
-  protected Iterable<AccountState> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<AccountState> fetchItems() throws Exception {
     return accounts.all();
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, AccountState as, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(AccountState as, Timestamp sinceTs) {
     try {
       Account a = as.getAccount();
       Timestamp accountTs = a.getRegisteredOn();
@@ -63,7 +61,7 @@
         accountIdx.index(a.getId(), Operation.INDEX, Optional.empty());
         return Optional.of(accountTs);
       }
-    } catch (IOException | OrmException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
index 67c8325..dd323b4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
@@ -21,13 +21,11 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.sql.Timestamp;
@@ -77,14 +75,14 @@
   }
 
   @Override
-  protected Iterable<Change> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<Change> fetchItems() throws Exception {
     Stream<Change> allChangesStream = Stream.empty();
     Iterable<Project.NameKey> projects = projectCache.all();
     for (Project.NameKey projectName : projects) {
       try (Repository repo = repoManager.openRepository(projectName)) {
         Stream<Change> projectChangesStream =
             notesFactory
-                .scan(repo, db, projectName)
+                .scan(repo, projectName)
                 .map((ChangeNotesResult changeNotes) -> changeNotes.notes().getChange());
         allChangesStream = Streams.concat(allChangesStream, projectChangesStream);
       }
@@ -93,7 +91,7 @@
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Change c, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(Change c, Timestamp sinceTs) {
     try {
       Timestamp changeTs = c.getLastUpdatedOn();
       if (changeTs.after(sinceTs)) {
@@ -102,7 +100,7 @@
         changeIdx.index(c.getProject() + "~" + c.getId(), Operation.INDEX, Optional.empty());
         return Optional.of(changeTs);
       }
-    } catch (OrmException | IOException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
index 71a0280..e37234c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.group.db.Groups;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -34,12 +33,12 @@
   }
 
   @Override
-  protected Iterable<GroupReference> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<GroupReference> fetchItems() throws Exception {
     return groups.getAllGroupReferences()::iterator;
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, GroupReference g, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(GroupReference g, Timestamp sinceTs) {
     return Optional.empty();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
index 8abdb59..561bb19 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
@@ -23,11 +23,9 @@
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -54,7 +52,6 @@
   private final FlusherRunner accountFlusher;
   private final FlusherRunner groupFlusher;
   private final FlusherRunner projectFlusher;
-  private final SchemaFactory<ReviewDb> schemaFactory;
   private final ChangeFinder changeFinder;
   private final CurrentRequestContext currCtx;
 
@@ -104,7 +101,6 @@
   public IndexTs(
       @PluginData Path dataDir,
       WorkQueue queue,
-      SchemaFactory<ReviewDb> schemaFactory,
       ChangeFinder changeFinder,
       CurrentRequestContext currCtx) {
     this.dataDir = dataDir;
@@ -113,7 +109,6 @@
     this.accountFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.ACCOUNT);
     this.groupFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.GROUP);
     this.projectFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.PROJECT);
-    this.schemaFactory = schemaFactory;
     this.changeFinder = changeFinder;
     this.currCtx = currCtx;
   }
@@ -137,7 +132,7 @@
   public void onChangeIndexed(String projectName, int id) {
     currCtx.onlyWithContext(
         (ctx) -> {
-          try (ReviewDb db = schemaFactory.open()) {
+          try {
             ChangeNotes changeNotes = changeFinder.findOne(projectName + "~" + id);
             update(
                 IndexName.CHANGE,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
index 582227d..ff5a965 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -35,12 +34,12 @@
   }
 
   @Override
-  protected Iterable<Project.NameKey> fetchItems(ReviewDb db) {
+  protected Iterable<Project.NameKey> fetchItems() {
     return projectCache.all();
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Project.NameKey g, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(Project.NameKey g, Timestamp sinceTs) {
     return Optional.empty();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
index 7a5669e..3cb0ed0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
@@ -17,7 +17,6 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.base.Stopwatch;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -27,6 +26,7 @@
 import java.util.concurrent.TimeUnit;
 
 abstract class ReindexRunnable<T> implements Runnable {
+
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final AbstractIndexRestApiServlet.IndexName itemName;
@@ -49,14 +49,13 @@
     if (maybeIndexTs.isPresent()) {
       newLastIndexTs = maxTimestamp(newLastIndexTs, Timestamp.valueOf(maybeIndexTs.get()));
       log.atFine().log("Scanning for all the %ss after %s", itemNameString, newLastIndexTs);
-      try (ManualRequestContext mctx = ctx.open();
-          ReviewDb db = mctx.getReviewDbProvider().get()) {
+      try (ManualRequestContext mctx = ctx.open()) {
         int count = 0;
         int errors = 0;
         Stopwatch stopwatch = Stopwatch.createStarted();
-        for (T c : fetchItems(db)) {
+        for (T c : fetchItems()) {
           try {
-            Optional<Timestamp> itemTs = indexIfNeeded(db, c, newLastIndexTs);
+            Optional<Timestamp> itemTs = indexIfNeeded(c, newLastIndexTs);
             if (itemTs.isPresent()) {
               count++;
               newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get());
@@ -102,7 +101,7 @@
     return ts2;
   }
 
-  protected abstract Iterable<T> fetchItems(ReviewDb db) throws Exception;
+  protected abstract Iterable<T> fetchItems() throws Exception;
 
-  protected abstract Optional<Timestamp> indexIfNeeded(ReviewDb db, T item, Timestamp sinceTs);
+  protected abstract Optional<Timestamp> indexIfNeeded(T item, Timestamp sinceTs);
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
index 27cb5c2..0f43eaf 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
@@ -26,14 +26,18 @@
 @Singleton
 class CachePatternMatcher {
   private static final List<String> DEFAULT_PATTERNS =
-      ImmutableList.of(
-          "accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys", "web_sessions");
+      ImmutableList.of("accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys");
 
   private final Pattern pattern;
 
   @Inject
   CachePatternMatcher(Configuration cfg) {
     List<String> patterns = new ArrayList<>(DEFAULT_PATTERNS);
+
+    if (cfg.websession().synchronize()) {
+      patterns.add("web_sessions");
+    }
+
     patterns.addAll(cfg.cache().patterns());
     this.pattern = Pattern.compile(Joiner.on("|").join(patterns));
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java
new file mode 100644
index 0000000..8c00282
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.event;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+
+public class ProjectNameKeyAdapter
+    implements JsonSerializer<Project.NameKey>, JsonDeserializer<Project.NameKey> {
+  @Override
+  public JsonElement serialize(
+      Project.NameKey project, Type typeOfSrc, JsonSerializationContext context) {
+    return new JsonPrimitive(project.get());
+  }
+
+  @Override
+  public Project.NameKey deserialize(
+      JsonElement json, Type typeOfT, JsonDeserializationContext context)
+      throws JsonParseException {
+    if (json.isJsonObject()) {
+      return Project.nameKey(json.getAsJsonObject().get("name").getAsJsonPrimitive().getAsString());
+    }
+    return Project.nameKey(json.getAsString());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
index 3d9fd60..f156da4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
@@ -14,7 +14,6 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventBroker;
 import com.google.gerrit.server.events.EventListener;
@@ -24,7 +23,6 @@
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 class ForwardedAwareEventBroker extends EventBroker {
 
@@ -34,15 +32,8 @@
       PluginSetContext<EventListener> unrestrictedListeners,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
-      Factory notesFactory,
-      Provider<ReviewDb> dbProvider) {
-    super(
-        listeners,
-        unrestrictedListeners,
-        permissionBackend,
-        projectCache,
-        notesFactory,
-        dbProvider);
+      Factory notesFactory) {
+    super(listeners, unrestrictedListeners, permissionBackend, projectCache, notesFactory);
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
index 51db006..414e795 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -42,9 +41,8 @@
    * Dispatch an event in the local node, event will not be forwarded to the other node.
    *
    * @param event The event to dispatch
-   * @throws OrmException If an error occur while retrieving the change the event belongs to.
    */
-  public void dispatch(Event event) throws OrmException, PermissionBackendException {
+  public void dispatch(Event event) throws PermissionBackendException {
     try {
       Context.setForwardedEvent(true);
       log.atFine().log("dispatching event %s", event.getType());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
index ba60830..f31331d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,13 +31,12 @@
   private final AccountIndexer indexer;
 
   @Inject
-  ForwardedIndexAccountHandler(AccountIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexAccountHandler(AccountIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) throws IOException {
+  protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) {
     indexer.index(id);
     log.atFine().log("Account %s successfully indexed", id);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java
index a6cbb7f..dee8876 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexBatchChangeHandler.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl.Factory;
-import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
 import com.ericsson.gerrit.plugins.highavailability.index.ForwardedBatchIndexExecutor;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.util.OneOffRequestContext;
@@ -30,11 +29,10 @@
   @Inject
   ForwardedIndexBatchChangeHandler(
       ChangeIndexer indexer,
-      ChangeDb changeDb,
       Configuration config,
       @ForwardedBatchIndexExecutor ScheduledExecutorService indexExecutor,
       OneOffRequestContext oneOffCtx,
       Factory changeCheckerFactory) {
-    super(indexer, changeDb, config, indexExecutor, oneOffCtx, changeCheckerFactory);
+    super(indexer, config, indexExecutor, oneOffCtx, changeCheckerFactory);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
index d7c7e88..add93d7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -18,17 +18,14 @@
 import com.ericsson.gerrit.plugins.highavailability.Configuration.Index;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl;
-import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
 import com.ericsson.gerrit.plugins.highavailability.index.ForwardedIndexExecutor;
 import com.google.common.base.Splitter;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -45,7 +42,6 @@
 @Singleton
 public class ForwardedIndexChangeHandler extends ForwardedIndexingHandler<String> {
   private final ChangeIndexer indexer;
-  private final ChangeDb changeDb;
   private final ScheduledExecutorService indexExecutor;
   private final OneOffRequestContext oneOffCtx;
   private final int retryInterval;
@@ -55,14 +51,11 @@
   @Inject
   ForwardedIndexChangeHandler(
       ChangeIndexer indexer,
-      ChangeDb changeDb,
       Configuration config,
       @ForwardedIndexExecutor ScheduledExecutorService indexExecutor,
       OneOffRequestContext oneOffCtx,
       ChangeCheckerImpl.Factory changeCheckerFactory) {
-    super(config.index());
     this.indexer = indexer;
-    this.changeDb = changeDb;
     this.indexExecutor = indexExecutor;
     this.oneOffCtx = oneOffCtx;
     this.changeCheckerFactory = changeCheckerFactory;
@@ -73,13 +66,12 @@
   }
 
   @Override
-  protected void doIndex(String id, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException {
+  protected void doIndex(String id, Optional<IndexEvent> indexEvent) throws IOException {
     doIndex(id, indexEvent, 0);
   }
 
   private void doIndex(String id, Optional<IndexEvent> indexEvent, int retryCount)
-      throws IOException, OrmException {
+      throws IOException {
     try {
       ChangeChecker checker = changeCheckerFactory.create(id);
       Optional<ChangeNotes> changeNotes = checker.getChangeNotes();
@@ -121,11 +113,9 @@
     }
   }
 
-  private void reindex(ChangeNotes notes) throws IOException, OrmException {
-    try (ReviewDb db = changeDb.open()) {
-      notes.reload();
-      indexer.index(db, notes.getChange());
-    }
+  private void reindex(ChangeNotes notes) {
+    notes.reload();
+    indexer.index(notes.getChange());
   }
 
   private boolean rescheduleIndex(String id, Optional<IndexEvent> indexEvent, int retryCount) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
index fb4d2f2..127af36 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,14 +31,12 @@
   private final GroupIndexer indexer;
 
   @Inject
-  ForwardedIndexGroupHandler(GroupIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexGroupHandler(GroupIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent)
-      throws IOException {
+  protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent) {
     indexer.index(uuid);
     log.atFine().log("Group %s successfully indexed", uuid);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
index 5ae4cc0..b6daf41 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.index.project.ProjectIndexer;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,14 +31,12 @@
   private final ProjectIndexer indexer;
 
   @Inject
-  ForwardedIndexProjectHandler(ProjectIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexProjectHandler(ProjectIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent)
-      throws IOException {
+  protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent) {
     indexer.index(projectName);
     log.atFine().log("Project %s successfully indexed", projectName);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
index 78e103d..44e35a8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
@@ -14,13 +14,12 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.flogger.FluentLogger;
-import com.google.common.util.concurrent.Striped;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Optional;
-import java.util.concurrent.locks.Lock;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Base class to handle forwarded indexing. This class is meant to be extended by classes used on
@@ -30,6 +29,7 @@
  */
 public abstract class ForwardedIndexingHandler<T> {
   protected static final FluentLogger log = FluentLogger.forEnclosingClass();
+  private final Set<T> inFlightIndexing = Collections.newSetFromMap(new ConcurrentHashMap<>());
 
   public enum Operation {
     INDEX,
@@ -41,17 +41,10 @@
     }
   }
 
-  private final Striped<Lock> idLocks;
-
-  protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException;
+  protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent) throws IOException;
 
   protected abstract void doDelete(T id, Optional<IndexEvent> indexEvent) throws IOException;
 
-  protected ForwardedIndexingHandler(Configuration.Index indexConfig) {
-    idLocks = Striped.lock(indexConfig.numStripedLocks());
-  }
-
   /**
    * Index an item in the local node, indexing will not be forwarded to the other node.
    *
@@ -59,16 +52,12 @@
    * @param operation The operation to do; index or delete
    * @param indexEvent The index event details.
    * @throws IOException If an error occur while indexing.
-   * @throws OrmException If an error occur while retrieving a change related to the item to index
    */
-  public void index(T id, Operation operation, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException {
+  public void index(T id, Operation operation, Optional<IndexEvent> indexEvent) throws IOException {
     log.atFine().log("%s %s %s", operation, id, indexEvent);
-    try {
-      Context.setForwardedEvent(true);
-      Lock idLock = idLocks.get(id);
-      idLock.lock();
+    if (inFlightIndexing.add(id)) {
       try {
+        Context.setForwardedEvent(true);
         switch (operation) {
           case INDEX:
             doIndex(id, indexEvent);
@@ -81,10 +70,12 @@
             break;
         }
       } finally {
-        idLock.unlock();
+        Context.unsetForwardedEvent();
+        inFlightIndexing.remove(id);
       }
-    } finally {
-      Context.unsetForwardedEvent();
+    } else {
+      throw new InFlightIndexedException(
+          String.format("Indexing for %s %s %s already in flight", operation, id, indexEvent));
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
index 851d7e8..b73b676 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import com.google.gerrit.server.events.Event;
+import java.util.concurrent.CompletableFuture;
 
 /** Forward indexing, stream events and cache evictions to the other master */
 public interface Forwarder {
@@ -24,9 +25,10 @@
    *
    * @param accountId the account to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexAccount(int accountId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other master.
@@ -34,9 +36,10 @@
    * @param projectName the project of the change to index.
    * @param changeId the change to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexChange(String projectName, int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexChange(String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other master using batch index endpoint.
@@ -44,67 +47,76 @@
    * @param projectName the project of the change to index.
    * @param changeId the change to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean batchIndexChange(String projectName, int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> batchIndexChange(
+      String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a delete change from index event to the other master.
    *
    * @param changeId the change to remove from the index.
    * @param indexEvent the details of the index event.
-   * @return rue if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a group indexing event to the other master.
    *
    * @param uuid the group to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexGroup(String uuid, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent);
 
   /**
    * Forward a project indexing event to the other master.
    *
    * @param projectName the project to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexProject(String projectName, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent);
 
   /**
    * Forward a stream event to the other master.
    *
    * @param event the event to forward.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean send(Event event);
+  CompletableFuture<Boolean> send(Event event);
 
   /**
    * Forward a cache eviction event to the other master.
    *
    * @param cacheName the name of the cache to evict an entry from.
    * @param key the key identifying the entry to evict from the cache.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean evict(String cacheName, Object key);
+  CompletableFuture<Boolean> evict(String cacheName, Object key);
 
   /**
    * Forward an addition to the project list cache to the other master.
    *
    * @param projectName the name of the project to add to the project list cache
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean addToProjectList(String projectName);
+  CompletableFuture<Boolean> addToProjectList(String projectName);
 
   /**
    * Forward a removal from the project list cache to the other master.
    *
    * @param projectName the name of the project to remove from the project list cache
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean removeFromProjectList(String projectName);
+  CompletableFuture<Boolean> removeFromProjectList(String projectName);
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java
new file mode 100644
index 0000000..87d28b3
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import java.io.IOException;
+
+public class InFlightIndexedException extends IOException {
+
+  private static final long serialVersionUID = 1L;
+
+  public InFlightIndexedException(String msg) {
+    super(msg);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
index 410aa23..5603fc2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -16,7 +16,6 @@
 
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler;
@@ -25,7 +24,6 @@
 import com.google.common.base.Charsets;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.Optional;
@@ -93,10 +91,6 @@
     } catch (IOException e) {
       sendError(rsp, SC_CONFLICT, e.getMessage());
       log.atSevere().withCause(e).log("Unable to update %s index", indexName);
-    } catch (OrmException e) {
-      String msg = String.format("Error trying to find %s", indexName);
-      sendError(rsp, SC_NOT_FOUND, msg);
-      log.atFine().withCause(e).log(msg);
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
index 61b714c..3d2d25e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.net.MediaType.JSON_UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 
@@ -25,7 +24,6 @@
 import com.google.common.net.MediaType;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -55,9 +53,6 @@
       }
       forwardedEventHandler.dispatch(getEventFromRequest(req));
       rsp.setStatus(SC_NO_CONTENT);
-    } catch (OrmException e) {
-      log.atFine().withCause(e).log("Error trying to find a change");
-      sendError(rsp, SC_NOT_FOUND, "Change not found\n");
     } catch (IOException | PermissionBackendException e) {
       log.atSevere().withCause(e).log("Unable to re-trigger event");
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
index e62cec2..c600019 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
@@ -14,10 +14,13 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
+import com.ericsson.gerrit.plugins.highavailability.event.ProjectNameKeyAdapter;
 import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDeserializer;
 import com.google.gerrit.server.events.SupplierDeserializer;
+import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.inject.Provider;
@@ -28,6 +31,8 @@
   public Gson get() {
     return new GsonBuilder()
         .registerTypeAdapter(Event.class, new EventDeserializer())
+        .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeyAdapter())
+        .registerTypeAdapter(Supplier.class, new SupplierSerializer())
         .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
         .create();
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
index f2ac080..cf98aae 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -15,11 +15,9 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.google.common.base.Supplier;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.net.MediaType;
-import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.net.URI;
@@ -32,12 +30,12 @@
 
 class HttpSession {
   private final CloseableHttpClient httpClient;
-  private final Gson gson =
-      new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
+  private final Gson gson;
 
   @Inject
-  HttpSession(CloseableHttpClient httpClient) {
+  HttpSession(CloseableHttpClient httpClient, GsonProvider gsonProvider) {
     this.httpClient = httpClient;
+    this.gson = gsonProvider.get();
   }
 
   HttpResult post(String uri) throws IOException {
@@ -67,7 +65,8 @@
     }
   }
 
-  private String jsonEncode(Object content) {
+  @VisibleForTesting
+  String jsonEncode(Object content) {
     if (content instanceof String) {
       return (String) content;
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index 52065ed..6426b5a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -28,10 +28,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
 import javax.net.ssl.SSLException;
 import org.apache.http.HttpException;
 import org.apache.http.client.ClientProtocolException;
@@ -49,6 +47,7 @@
   private final Configuration cfg;
   private final Provider<Set<PeerInfo>> peerInfoProvider;
   private final GsonProvider gson;
+  private final RestForwarderScheduler scheduler;
 
   @Inject
   RestForwarder(
@@ -56,21 +55,24 @@
       @PluginName String pluginName,
       Configuration cfg,
       Provider<Set<PeerInfo>> peerInfoProvider,
-      GsonProvider gson) {
+      GsonProvider gson,
+      RestForwarderScheduler scheduler) {
     this.httpSession = httpClient;
     this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName);
     this.cfg = cfg;
     this.peerInfoProvider = peerInfoProvider;
     this.gson = gson;
+    this.scheduler = scheduler;
   }
 
   @Override
-  public boolean indexAccount(final int accountId, IndexEvent event) {
+  public CompletableFuture<Boolean> indexAccount(final int accountId, IndexEvent event) {
     return execute(RequestMethod.POST, "index account", "index/account", accountId, event);
   }
 
   @Override
-  public boolean indexChange(String projectName, int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> indexChange(
+      String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         "index change",
@@ -79,8 +81,8 @@
         event);
   }
 
-  @Override
-  public boolean batchIndexChange(String projectName, int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> batchIndexChange(
+      String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         "index change",
@@ -90,13 +92,13 @@
   }
 
   @Override
-  public boolean deleteChangeFromIndex(final int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> deleteChangeFromIndex(final int changeId, IndexEvent event) {
     return execute(
         RequestMethod.DELETE, "delete change", "index/change", buildIndexEndpoint(changeId), event);
   }
 
   @Override
-  public boolean indexGroup(final String uuid, IndexEvent event) {
+  public CompletableFuture<Boolean> indexGroup(final String uuid, IndexEvent event) {
     return execute(RequestMethod.POST, "index group", "index/group", uuid, event);
   }
 
@@ -110,24 +112,24 @@
   }
 
   @Override
-  public boolean indexProject(String projectName, IndexEvent event) {
+  public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent event) {
     return execute(
         RequestMethod.POST, "index project", "index/project", Url.encode(projectName), event);
   }
 
   @Override
-  public boolean send(final Event event) {
+  public CompletableFuture<Boolean> send(final Event event) {
     return execute(RequestMethod.POST, "send event", "event", event.type, event);
   }
 
   @Override
-  public boolean evict(final String cacheName, final Object key) {
+  public CompletableFuture<Boolean> evict(final String cacheName, final Object key) {
     String json = gson.get().toJson(key);
     return execute(RequestMethod.POST, "invalidate cache " + cacheName, "cache", cacheName, json);
   }
 
   @Override
-  public boolean addToProjectList(String projectName) {
+  public CompletableFuture<Boolean> addToProjectList(String projectName) {
     return execute(
         RequestMethod.POST,
         "Update project_list, add ",
@@ -136,7 +138,7 @@
   }
 
   @Override
-  public boolean removeFromProjectList(String projectName) {
+  public CompletableFuture<Boolean> removeFromProjectList(String projectName) {
     return execute(
         RequestMethod.DELETE,
         "Update project_list, remove ",
@@ -148,19 +150,19 @@
     return Joiner.on("/").join("cache", Constants.PROJECT_LIST);
   }
 
-  private boolean execute(RequestMethod method, String action, String endpoint, Object id) {
+  private CompletableFuture<Boolean> execute(
+      RequestMethod method, String action, String endpoint, Object id) {
     return execute(method, action, endpoint, id, null);
   }
 
-  private boolean execute(
+  private CompletableFuture<Boolean> execute(
       RequestMethod method, String action, String endpoint, Object id, Object payload) {
-    List<CompletableFuture<Boolean>> futures =
-        peerInfoProvider.get().stream()
-            .map(peer -> createRequest(method, peer, action, endpoint, id, payload))
-            .map(request -> CompletableFuture.supplyAsync(request::execute))
-            .collect(Collectors.toList());
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-    return futures.stream().allMatch(CompletableFuture::join);
+    return peerInfoProvider.get().stream()
+        .map(peer -> createRequest(method, peer, action, endpoint, id, payload))
+        .map(scheduler::execute)
+        .reduce(
+            CompletableFuture.completedFuture(true),
+            (a, b) -> a.thenCombine(b, (left, right) -> left && right));
   }
 
   private Request createRequest(
@@ -186,7 +188,7 @@
     };
   }
 
-  private abstract class Request {
+  protected abstract class Request {
     private final String action;
     private final Object key;
     private final String destination;
@@ -199,42 +201,36 @@
       this.destination = destination;
     }
 
-    boolean execute() {
-      log.atFine().log("Executing %s %s towards %s", action, key, destination);
-      for (; ; ) {
-        try {
-          execCnt++;
-          tryOnce();
-          log.atFine().log("%s %s towards %s OK", action, key, destination);
-          return true;
-        } catch (ForwardingException e) {
-          int maxTries = cfg.http().maxTries();
-          log.atFine().withCause(e).log(
-              "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries);
-          if (!e.isRecoverable()) {
-            log.atSevere().withCause(e).log(
-                "%s %s towards %s failed with unrecoverable error; giving up",
-                action, key, destination);
-            return false;
-          }
-          if (execCnt >= maxTries) {
-            log.atSevere().log(
-                "Failed to %s %s on %s after %d tries; giving up",
-                action, key, destination, maxTries);
-            return false;
-          }
+    @Override
+    public String toString() {
+      return String.format("%s:%s => %s (try #%d)", action, key, destination, execCnt);
+    }
 
-          log.atFine().log("Retrying to %s %s on %s", action, key, destination);
-          try {
-            Thread.sleep(cfg.http().retryInterval());
-          } catch (InterruptedException ie) {
-            log.atSevere().withCause(ie).log(
-                "%s %s towards %s was interrupted; giving up", action, key, destination);
-            Thread.currentThread().interrupt();
-            return false;
-          }
+    boolean execute() throws ForwardingException {
+      log.atFine().log("Executing %s %s towards %s", action, key, destination);
+      try {
+        execCnt++;
+        tryOnce();
+        log.atFine().log("%s %s towards %s OK", action, key, destination);
+        return true;
+      } catch (ForwardingException e) {
+        int maxTries = cfg.http().maxTries();
+        log.atFine().withCause(e).log(
+            "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries);
+        if (!e.isRecoverable()) {
+          log.atSevere().withCause(e).log(
+              "%s %s towards %s failed with unrecoverable error; giving up",
+              action, key, destination);
+          throw e;
+        }
+        if (execCnt >= maxTries) {
+          log.atSevere().log(
+              "Failed to %s %s on %s after %d tries; giving up",
+              action, key, destination, maxTries);
+          throw e;
         }
       }
+      return false;
     }
 
     void tryOnce() throws ForwardingException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java
new file mode 100644
index 0000000..ebbd940
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java
@@ -0,0 +1,146 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Singleton
+public class RestForwarderScheduler {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+  private final ScheduledExecutorService executor;
+  private final long retryIntervalMs;
+
+  public class CompletablePromise<V> extends CompletableFuture<V> {
+    private Future<V> future;
+
+    public CompletablePromise(Future<V> future) {
+      this.future = future;
+      executor.execute(this::tryToComplete);
+    }
+
+    private void tryToComplete() {
+      if (future.isDone()) {
+        try {
+          complete(future.get());
+        } catch (InterruptedException e) {
+          completeExceptionally(e);
+        } catch (ExecutionException e) {
+          completeExceptionally(e.getCause());
+        }
+        return;
+      }
+
+      if (future.isCancelled()) {
+        cancel(true);
+        return;
+      }
+
+      executor.execute(this::tryToComplete);
+    }
+  }
+
+  @Inject
+  public RestForwarderScheduler(
+      WorkQueue workQueue, Configuration cfg, Provider<Set<PeerInfo>> peerInfoProvider) {
+    int executorSize = peerInfoProvider.get().size() * cfg.index().threadPoolSize();
+    retryIntervalMs = cfg.index().retryInterval();
+    this.executor = workQueue.createQueue(executorSize, "RestForwarderScheduler");
+  }
+
+  @VisibleForTesting
+  public RestForwarderScheduler(ScheduledExecutorService executor) {
+    this.executor = executor;
+    retryIntervalMs = 0;
+  }
+
+  public CompletableFuture<Boolean> execute(RestForwarder.Request request) {
+    return execute(request, 0);
+  }
+
+  public CompletableFuture<Boolean> execute(RestForwarder.Request request, long delayMs) {
+    return supplyAsync(
+        request.toString(),
+        () -> {
+          try {
+            if (!request.execute()) {
+              log.atWarning().log(
+                  "Rescheduling %s for retry after %d msec", request, retryIntervalMs);
+              return execute(request, retryIntervalMs);
+            }
+            return CompletableFuture.completedFuture(true);
+          } catch (ForwardingException e) {
+            log.atSevere().withCause(e).log("Forwarding of %s has failed", request);
+            return CompletableFuture.completedFuture(false);
+          }
+        },
+        executor,
+        delayMs);
+  }
+
+  private CompletableFuture<Boolean> supplyAsync(
+      String taskName,
+      Supplier<CompletableFuture<Boolean>> fn,
+      ScheduledExecutorService executor,
+      long delayMs) {
+    BooleanAsyncSupplier asyncSupplier = new BooleanAsyncSupplier(taskName, fn);
+    executor.schedule(asyncSupplier, delayMs, TimeUnit.MILLISECONDS);
+    return asyncSupplier.future();
+  }
+
+  static class BooleanAsyncSupplier implements Runnable {
+    private CompletableFuture<CompletableFuture<Boolean>> dep;
+    private Supplier<CompletableFuture<Boolean>> fn;
+    private String taskName;
+
+    BooleanAsyncSupplier(String taskName, Supplier<CompletableFuture<Boolean>> fn) {
+      this.taskName = taskName;
+      this.dep = new CompletableFuture<>();
+      this.fn = fn;
+    }
+
+    public CompletableFuture<Boolean> future() {
+      return dep.thenCompose(Function.identity());
+    }
+
+    @Override
+    public void run() {
+      try {
+        dep.complete(fn.get());
+      } catch (Throwable ex) {
+        dep.completeExceptionally(ex);
+      }
+    }
+
+    @Override
+    public String toString() {
+      return taskName;
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
index ce04589..ef7f942 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 
@@ -27,18 +26,16 @@
    * Return the Change notes read from ReviewDb or NoteDb.
    *
    * @return notes of the Change
-   * @throws OrmException if ReviewDb or NoteDb cannot be opened
    */
-  Optional<ChangeNotes> getChangeNotes() throws OrmException;
+  Optional<ChangeNotes> getChangeNotes();
 
   /**
    * Create a new index event POJO associated with the current Change.
    *
    * @return new IndexEvent
    * @throws IOException if the current Change cannot read
-   * @throws OrmException if ReviewDb cannot be opened
    */
-  Optional<IndexEvent> newIndexEvent() throws IOException, OrmException;
+  Optional<IndexEvent> newIndexEvent() throws IOException;
 
   /**
    * Check if the local Change is aligned with the indexEvent received.
@@ -46,9 +43,8 @@
    * @param indexEvent indexing event
    * @return true if the local Change is up-to-date, false otherwise.
    * @throws IOException if an I/O error occurred while reading the local Change
-   * @throws OrmException if the local ReviewDb cannot be opened
    */
-  boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException, OrmException;
+  boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException;
 
   /**
    * Return the last computed up-to-date Change time-stamp.
@@ -57,7 +53,6 @@
    *
    * @return the Change timestamp epoch in seconds
    * @throws IOException if an I/O error occurred while reading the local Change
-   * @throws OrmException if the local ReviewDb cannot be opened
    */
-  Optional<Long> getComputedChangeTs() throws IOException, OrmException;
+  Optional<Long> getComputedChangeTs() throws IOException;
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
index a1b797f..1656eb9 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -19,15 +19,12 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
@@ -41,7 +38,6 @@
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final GitRepositoryManager gitRepoMgr;
   private final CommentsUtil commentsUtil;
-  private final ChangeDb changeDb;
   private final OneOffRequestContext oneOffReqCtx;
   private final String changeId;
   private final ChangeFinder changeFinder;
@@ -56,20 +52,18 @@
   public ChangeCheckerImpl(
       GitRepositoryManager gitRepoMgr,
       CommentsUtil commentsUtil,
-      ChangeDb changeDb,
       ChangeFinder changeFinder,
       OneOffRequestContext oneOffReqCtx,
       @Assisted String changeId) {
     this.changeFinder = changeFinder;
     this.gitRepoMgr = gitRepoMgr;
     this.commentsUtil = commentsUtil;
-    this.changeDb = changeDb;
     this.oneOffReqCtx = oneOffReqCtx;
     this.changeId = changeId;
   }
 
   @Override
-  public Optional<IndexEvent> newIndexEvent() throws IOException, OrmException {
+  public Optional<IndexEvent> newIndexEvent() throws IOException {
     Optional<Long> changeTs = getComputedChangeTs();
     if (!changeTs.isPresent()) {
       return Optional.empty();
@@ -91,7 +85,7 @@
   }
 
   @Override
-  public Optional<ChangeNotes> getChangeNotes() throws OrmException {
+  public Optional<ChangeNotes> getChangeNotes() {
     try (ManualRequestContext ctx = oneOffReqCtx.open()) {
       changeNotes = Optional.ofNullable(changeFinder.findOne(changeId));
       return changeNotes;
@@ -99,8 +93,7 @@
   }
 
   @Override
-  public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption)
-      throws IOException, OrmException {
+  public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption) throws IOException {
     getComputedChangeTs();
     log.atFine().log("Checking change %s against index event %s", this, indexEventOption);
     if (!computedChangeTs.isPresent()) {
@@ -128,7 +121,7 @@
   }
 
   @Override
-  public Optional<Long> getComputedChangeTs() throws IOException, OrmException {
+  public Optional<Long> getComputedChangeTs() {
     if (!computedChangeTs.isPresent()) {
       computedChangeTs = computeLastChangeTs();
     }
@@ -146,7 +139,7 @@
           + getBranchTargetSha(repo)
           + "/meta:"
           + getMetaSha(repo);
-    } catch (IOException | OrmException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Unable to render change %s", changeId);
       return "change-id=" + changeId;
     }
@@ -168,36 +161,26 @@
     }
   }
 
+  private Optional<Long> computeLastChangeTs() {
+    return getChangeNotes().map(this::getTsFromChangeAndDraftComments);
+  }
+
   private String getMetaSha(Repository repo) throws IOException {
-    if (PrimaryStorage.NOTE_DB.equals(PrimaryStorage.of(changeNotes.get().getChange()))) {
-      String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId());
-      Ref ref = repo.exactRef(refName);
-      if (ref == null) {
-        throw new IOException(
-            String.format("Unable to find meta ref %s for change %s", refName, changeId));
-      }
-      return ref.getTarget().getObjectId().getName();
+    String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId());
+    Ref ref = repo.exactRef(refName);
+    if (ref == null) {
+      throw new IOException(
+          String.format("Unable to find meta ref %s for change %s", refName, changeId));
     }
-
-    return null;
+    return ref.getTarget().getObjectId().getName();
   }
 
-  private Optional<Long> computeLastChangeTs() throws OrmException {
-    try (ReviewDb db = changeDb.open()) {
-      return getChangeNotes().map(notes -> getTsFromChangeAndDraftComments(db, notes));
-    }
-  }
-
-  private long getTsFromChangeAndDraftComments(ReviewDb db, ChangeNotes notes) {
+  private long getTsFromChangeAndDraftComments(ChangeNotes notes) {
     Change change = notes.getChange();
     Timestamp changeTs = change.getLastUpdatedOn();
-    try {
-      for (Comment comment : commentsUtil.draftByChange(db, changeNotes.get())) {
-        Timestamp commentTs = comment.writtenOn;
-        changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
-      }
-    } catch (OrmException e) {
-      log.atWarning().withCause(e).log("Unable to access draft comments for change %s", change);
+    for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) {
+      Timestamp commentTs = comment.writtenOn;
+      changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
     }
     return changeTs.getTime() / 1000;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java
deleted file mode 100644
index bef5363..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.ericsson.gerrit.plugins.highavailability.index;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-
-public class ChangeDb {
-  private static final DisabledReviewDb disabledReviewDb = new DisabledReviewDb();
-
-  private final NotesMigration migration;
-  private final SchemaFactory<ReviewDb> schemaFactory;
-
-  @Inject
-  public ChangeDb(NotesMigration migration, SchemaFactory<ReviewDb> schemaFactory) {
-    this.migration = migration;
-    this.schemaFactory = schemaFactory;
-  }
-
-  public ReviewDb open() throws OrmException {
-    return migration.readChanges() ? disabledReviewDb : schemaFactory.open();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
index f8c6e31..46aadbb 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.function.Consumer;
@@ -51,8 +50,6 @@
     if (ctx == null) {
       try (ManualRequestContext manualCtx = oneOffCtx.open()) {
         body.accept(manualCtx);
-      } catch (OrmException e) {
-        logger.atSevere().withCause(e).log("Unable to open request context");
       }
     } else {
       body.accept(ctx);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
deleted file mode 100644
index 48c6ecb..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.ericsson.gerrit.plugins.highavailability.index;
-
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
-import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
-import com.google.gerrit.reviewdb.server.PatchSetAccess;
-import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.SchemaVersionAccess;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.gwtorm.server.StatementExecutor;
-import java.util.Map;
-
-/** ReviewDb that is disabled. */
-@SuppressWarnings("deprecation")
-public class DisabledReviewDb implements ReviewDb {
-  public static class Disabled extends RuntimeException {
-    private static final long serialVersionUID = 1L;
-
-    private Disabled() {
-      super("ReviewDb is disabled for changes");
-    }
-  }
-
-  public static class DisabledChangeAccess implements ChangeAccess {
-
-    @Override
-    public String getRelationName() {
-      throw new Disabled();
-    }
-
-    @Override
-    public int getRelationID() {
-      throw new Disabled();
-    }
-
-    @Override
-    public ResultSet<Change> iterateAllEntities() throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change.Id primaryKey(Change entity) {
-      throw new Disabled();
-    }
-
-    @Override
-    public Map<Change.Id, Change> toMap(Iterable<Change> c) {
-      throw new Disabled();
-    }
-
-    @Override
-    public CheckedFuture<Change, OrmException> getAsync(Change.Id key) {
-      throw new Disabled();
-    }
-
-    @Override
-    public ResultSet<Change> get(Iterable<Change.Id> keys) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void insert(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void update(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void upsert(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void deleteKeys(Iterable<Change.Id> keys) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void delete(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void beginTransaction(Change.Id key) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change atomicUpdate(Change.Id key, AtomicUpdate<Change> update) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change get(Change.Id id) throws OrmException {
-      return null;
-    }
-
-    @Override
-    public ResultSet<Change> all() throws OrmException {
-      return null;
-    }
-  }
-
-  @Override
-  public void close() {
-    // Do nothing.
-  }
-
-  @Override
-  public void commit() {
-    throw new Disabled();
-  }
-
-  @Override
-  public void rollback() {
-    throw new Disabled();
-  }
-
-  @Override
-  public void updateSchema(StatementExecutor e) {
-    throw new Disabled();
-  }
-
-  @Override
-  public void pruneSchema(StatementExecutor e) {
-    throw new Disabled();
-  }
-
-  @Override
-  public Access<?, ?>[] allRelations() {
-    throw new Disabled();
-  }
-
-  @Override
-  public SchemaVersionAccess schemaVersion() {
-    throw new Disabled();
-  }
-
-  @Override
-  public ChangeAccess changes() {
-    return new DisabledChangeAccess();
-  }
-
-  @Override
-  public PatchSetApprovalAccess patchSetApprovals() {
-    throw new Disabled();
-  }
-
-  @Override
-  public ChangeMessageAccess changeMessages() {
-    throw new Disabled();
-  }
-
-  @Override
-  public PatchSetAccess patchSets() {
-    throw new Disabled();
-  }
-
-  @Override
-  public PatchLineCommentAccess patchComments() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextAccountId() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextAccountGroupId() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextChangeId() {
-    throw new Disabled();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
index 068ef0d..73e842f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -14,6 +14,7 @@
 
 package com.ericsson.gerrit.plugins.highavailability.index;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
@@ -27,8 +28,10 @@
 import com.google.inject.Inject;
 import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 class IndexEventHandler
     implements ChangeIndexedListener,
@@ -36,28 +39,37 @@
         GroupIndexedListener,
         ProjectIndexedListener {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
-  private final Executor executor;
-  private final Executor batchExecutor;
+  private final ScheduledExecutorService executor;
+  private final ScheduledExecutorService batchExecutor;
   private final Forwarder forwarder;
   private final String pluginName;
   private final Set<IndexTask> queuedTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
   private final ChangeCheckerImpl.Factory changeChecker;
   private final CurrentRequestContext currCtx;
+  private final IndexEventLocks locks;
+
+  private final int retryInterval;
+  private final int maxTries;
 
   @Inject
   IndexEventHandler(
-      @IndexExecutor Executor executor,
-      @BatchIndexExecutor Executor batchExecutor,
+      @IndexExecutor ScheduledExecutorService executor,
+      @BatchIndexExecutor ScheduledExecutorService batchExecutor,
       @PluginName String pluginName,
       Forwarder forwarder,
       ChangeCheckerImpl.Factory changeChecker,
-      CurrentRequestContext currCtx) {
+      CurrentRequestContext currCtx,
+      Configuration cfg,
+      IndexEventLocks locks) {
     this.forwarder = forwarder;
     this.executor = executor;
     this.batchExecutor = batchExecutor;
     this.pluginName = pluginName;
     this.changeChecker = changeChecker;
     this.currCtx = currCtx;
+    this.locks = locks;
+    this.retryInterval = cfg.http().retryInterval();
+    this.maxTries = cfg.http().maxTries();
   }
 
   @Override
@@ -141,6 +153,7 @@
 
   abstract class IndexTask implements Runnable {
     protected final IndexEvent indexEvent;
+    private int retryCount = 0;
 
     IndexTask() {
       indexEvent = new IndexEvent();
@@ -152,11 +165,27 @@
 
     @Override
     public void run() {
-      queuedTasks.remove(this);
-      execute();
+      locks.withLock(
+          this,
+          () -> {
+            queuedTasks.remove(this);
+            return execute();
+          },
+          this::reschedule);
     }
 
-    abstract void execute();
+    private void reschedule() {
+      if (++retryCount <= maxTries) {
+        log.atFine().log("Retrying %d times to %s", retryCount, this);
+        executor.schedule(this, retryInterval, TimeUnit.MILLISECONDS);
+      } else {
+        log.atSevere().log("Failed to %s after %d tries; giving up", this, maxTries);
+      }
+    }
+
+    abstract CompletableFuture<Boolean> execute();
+
+    abstract String indexId();
   }
 
   class IndexChangeTask extends IndexTask {
@@ -170,8 +199,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexChange(projectName, changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexChange(projectName, changeId, indexEvent);
     }
 
     @Override
@@ -192,6 +221,11 @@
     public String toString() {
       return String.format("[%s] Index change %s in target instance", pluginName, changeId);
     }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
+    }
   }
 
   class BatchIndexChangeTask extends IndexTask {
@@ -205,8 +239,13 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.batchIndexChange(projectName, changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.batchIndexChange(projectName, changeId, indexEvent);
+    }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
     }
 
     @Override
@@ -238,8 +277,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.deleteChangeFromIndex(changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.deleteChangeFromIndex(changeId, indexEvent);
     }
 
     @Override
@@ -260,6 +299,11 @@
     public String toString() {
       return String.format("[%s] Delete change %s in target instance", pluginName, changeId);
     }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
+    }
   }
 
   class IndexAccountTask extends IndexTask {
@@ -270,8 +314,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexAccount(accountId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexAccount(accountId, indexEvent);
     }
 
     @Override
@@ -292,6 +336,11 @@
     public String toString() {
       return String.format("[%s] Index account %s in target instance", pluginName, accountId);
     }
+
+    @Override
+    String indexId() {
+      return "account/" + accountId;
+    }
   }
 
   class IndexGroupTask extends IndexTask {
@@ -302,8 +351,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexGroup(groupUUID, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexGroup(groupUUID, indexEvent);
     }
 
     @Override
@@ -324,6 +373,11 @@
     public String toString() {
       return String.format("[%s] Index group %s in target instance", pluginName, groupUUID);
     }
+
+    @Override
+    String indexId() {
+      return "group/" + groupUUID;
+    }
   }
 
   class IndexProjectTask extends IndexTask {
@@ -334,8 +388,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexProject(projectName, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexProject(projectName, indexEvent);
     }
 
     @Override
@@ -356,5 +410,10 @@
     public String toString() {
       return String.format("[%s] Index project %s in target instance", pluginName, projectName);
     }
+
+    @Override
+    String indexId() {
+      return "project/" + projectName;
+    }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java
new file mode 100644
index 0000000..3fd26db
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java
@@ -0,0 +1,95 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.index;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.common.util.concurrent.Striped;
+import com.google.inject.Inject;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class IndexEventLocks {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+  private static final int NUMBER_OF_INDEX_TASK_TYPES = 4;
+  private static final int WAIT_TIMEOUT_MS = 5;
+
+  private final Striped<Semaphore> semaphores;
+
+  @Inject
+  public IndexEventLocks(Configuration cfg) {
+    this.semaphores =
+        Striped.semaphore(NUMBER_OF_INDEX_TASK_TYPES * cfg.index().numStripedLocks(), 1);
+  }
+
+  public CompletableFuture<?> withLock(
+      IndexTask id, IndexCallFunction function, VoidFunction lockAcquireTimeoutCallback) {
+    String indexId = id.indexId();
+    Semaphore idSemaphore = getSemaphore(indexId);
+    try {
+      log.atFine().log("Trying to acquire %s", id);
+      if (idSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+        log.atFine().log("Acquired %s", id);
+        return function
+            .invoke()
+            .whenComplete(
+                (result, error) -> {
+                  try {
+                    log.atFine().log("Trying to release %s", id);
+                    idSemaphore.release();
+                    log.atFine().log("Released %s", id);
+                  } catch (Throwable t) {
+                    log.atSevere().withCause(t).log("Unable to release %s", id);
+                    throw t;
+                  }
+                });
+      }
+
+      String timeoutMessage =
+          String.format(
+              "Acquisition of the locking of %s timed out after %d msec: consider increasing the number of shards",
+              indexId, WAIT_TIMEOUT_MS);
+      log.atWarning().log(timeoutMessage);
+      lockAcquireTimeoutCallback.invoke();
+      CompletableFuture<?> failureFuture = new CompletableFuture<>();
+      failureFuture.completeExceptionally(new InterruptedException(timeoutMessage));
+      return failureFuture;
+    } catch (InterruptedException e) {
+      CompletableFuture<?> failureFuture = new CompletableFuture<>();
+      failureFuture.completeExceptionally(e);
+      log.atSevere().withCause(e).log("Locking of %s was interrupted; giving up", indexId);
+      return failureFuture;
+    }
+  }
+
+  @VisibleForTesting
+  protected Semaphore getSemaphore(String indexId) {
+    return semaphores.get(indexId);
+  }
+
+  @FunctionalInterface
+  public interface VoidFunction {
+    void invoke();
+  }
+
+  @FunctionalInterface
+  public interface IndexCallFunction {
+    CompletableFuture<?> invoke();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
index 4258dce..3bcc34f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -20,26 +20,31 @@
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Scopes;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
-import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
 public class IndexModule extends LifecycleModule {
 
   @Override
   protected void configure() {
-    bind(Executor.class).annotatedWith(IndexExecutor.class).toProvider(IndexExecutorProvider.class);
-    bind(Executor.class)
-        .annotatedWith(BatchIndexExecutor.class)
-        .toProvider(BatchIndexExecutorProvider.class);
+    bind(ScheduledExecutorService.class)
+        .annotatedWith(IndexExecutor.class)
+        .toProvider(IndexExecutorProvider.class);
     bind(ScheduledExecutorService.class)
         .annotatedWith(ForwardedIndexExecutor.class)
         .toProvider(ForwardedIndexExecutorProvider.class);
+    bind(IndexEventLocks.class).in(Scopes.SINGLETON);
+    bind(ScheduledExecutorService.class)
+        .annotatedWith(BatchIndexExecutor.class)
+        .toProvider(BatchIndexExecutorProvider.class);
     bind(ScheduledExecutorService.class)
         .annotatedWith(ForwardedBatchIndexExecutor.class)
         .toProvider(ForwardedBatchIndexExecutorProvider.class);
     listener().to(IndexExecutorProvider.class);
-    DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexEventHandler.class);
+    DynamicSet.bind(binder(), ChangeIndexedListener.class)
+        .to(IndexEventHandler.class)
+        .in(Scopes.SINGLETON);
     DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), ProjectIndexedListener.class).to(IndexEventHandler.class);
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index c7196c6..fd0b56e 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,20 +1,19 @@
 
 This plugin allows making Gerrit highly available by having redundant Gerrit
-masters.
+active/passive instances.
 
-The masters must be:
+The Gerrit instances must be:
 
-* connecting to the same database
 * sharing the git repositories using a shared file system (e.g. NFS)
 * behind a load balancer (e.g. HAProxy)
 
-Currently, the mode supported is one primary (active) master and multiple backup
-(passive) masters but eventually the plan is to support `n` active masters. In
-the active/passive mode, the active master is handling all traffic while the
-passives are kept updated to be always ready to take over.
+Currently, the mode supported is one active instance and multiple backup
+(passive) instances but eventually the plan is to support `n` active instances.
+In the active/passive mode, the active instance is handling all traffic while
+the passives are kept updated to be always ready to take over.
 
-Even if database and git repositories are shared by the masters, there are a few
-areas of concern in order to be able to switch traffic between masters in a
+Even if git repositories are shared by the instances, there are a few areas
+of concern in order to be able to switch traffic between instances in a
 transparent manner from the user's perspective. The 4 areas of concern are
 things that Gerrit stores either in memory or locally in the review site:
 
@@ -23,68 +22,67 @@
 * stream-events
 * web sessions
 
-They need either to be shared or kept local to each master but synchronized.
-This plugin needs to be installed in all the masters and it will take care of sharing
-or synchronizing them.
+They need either to be shared or kept local to each instances but synchronized.
+This plugin needs to be installed in all the instances and it will take care of
+sharing or synchronizing them.
 
 #### Caches
-Every time a cache eviction occurs in one of the masters, the eviction will be
-forwarded the other masters so their caches do not contain stale entries.
+Every time a cache eviction occurs in one of the instances, the eviction will be
+forwarded the other nodes so their caches do not contain stale entries.
 
 #### Secondary indexes
-Every time the secondary index is modified in one of the masters, e.g., a change
-is added, updated or removed from the index, the others master's index are
-updated accordingly. This way, both indexes are kept synchronized.
+Every time the secondary index is modified in one of the instances, e.g., a change
+is added, updated or removed from the index, the others instances index are updated
+accordingly. This way, both indexes are kept synchronized.
 
 #### Stream events
-Every time a stream event occurs in one of the masters (see [more events info]
-(https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)),
-the event is forwarded to the other masters which re-plays it. This way, the
-output of the stream-events command is the same, no matter which master a client
-is connected to.
+Every time a stream event occurs in one of the instances
+(see [more events info](https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)),
+the event is forwarded to the other instances which re-plays it. This way, the output
+of the stream-events command is the same, no matter which  instance a client is
+connected to.
 
 #### Web session
 The built-in Gerrit H2 based web session cache is replaced with a file based
-implementation that is shared amongst the masters.
+implementation that is shared amongst the instances.
 
 ## Setup
 
 Prerequisites:
 
-* Unique database server must be accessible from all the masters
 * Git repositories must be located on a shared file system
 * A directory on a shared file system must be available for @PLUGIN@ to use
 
-For the masters:
+For the instances:
 
-* Configure database section in gerrit.config to use the shared database
 * Configure gerrit.basePath in gerrit.config to the shared repositories location
-* Install and configure @PLUGIN@ plugin
+* Configure gerrit.serverId in gerrit.config based on [config](config.md)'s introduction
+* Install and configure this @PLUGIN@ plugin [further](config.md) or based on below.
 
 Here is an example of the minimal @PLUGIN@.config:
 
-Primary master
+Active instance
 
 ```
 [main]
-  sharedDirectory = /directory/accessible/from/both/masters
+  sharedDirectory = /directory/accessible/from/both/nodes
 
 [peerInfo "static"]
-  url = http://backupMasterHost1:8081/
+  url = http://backupNodeHost1:8081/
 
 [http]
   user = username
   password = password
 ```
 
-Backup master
+Backup instance
 
 ```
 [main]
-  sharedDirectory = /directory/accessible/from/both/masters
+  sharedDirectory = /directory/accessible/from/both/nodes
 
 [peerInfo "static"]
-  url = http://primaryMasterHost:8080/
+  url = http://primaryNodeHost:8080/
 
 [http]
   user = username
@@ -93,10 +91,10 @@
 
 ### HA replica site
 
-It is possible to create a copy of the master site and configure both sites to run
-in HA mode as peers. This is possible when the directory where the copy will be
-created is accessible from this machine. Such a replica site can be created by
-means of a gerrit [site init](../../../Documentation/pgm-init.html) step,
+It is possible to create a copy of the instance site and configure both
+sites to run in HA mode as peers. This is possible when the directory where
+the copy will be created is accessible from this machine. Such a replica site
+can be created by means of a gerrit [site init](../../../Documentation/pgm-init.html) step,
 contributed by the plugin under its init section.
 
 This init step is optional but defaults to creating the replica. If you want to
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index ea20e89..360218d 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -2,8 +2,14 @@
 @PLUGIN@ Configuration
 =========================
 
-The @PLUGIN@ plugin must be installed on all the instances and the following fields
-should be specified in `$site_path/etc/@PLUGIN@.config` file:
+The @PLUGIN@ plugin must be installed on all the instances. Each instance should
+be configured with the same [gerrit.serverId](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/config-gerrit.html#gerrit.serverId).
+If there are existing changes in [NoteDb](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/note-db.html)
+made with another `serverId`, then this plugin might not be able to access them.
+Likewise, if the HA gerrit.serverIds differ, then changes conveyed by one
+instance will not be accessible by the other.
+
+The following fields should be specified in `$site_path/etc/@PLUGIN@.config` files:
 
 File '@PLUGIN@.config'
 --------------------
@@ -49,7 +55,7 @@
 ```
 
 ```main.sharedDirectory```
-:   Path to a directory accessible from both master instances.
+:   Path to a directory accessible from both instances.
     When given as a relative path, then it is resolved against the $SITE_PATH
     or Gerrit server. For example, if $SITE_PATH is "/gerrit/root" and
     sharedDirectory is given as "shared/dir" then the real path of the shared
@@ -75,13 +81,6 @@
     Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval).
     When not specified, polling of conditional reindexing is disabled.
 
-```autoReindex.interval```
-:   Enable the tracking of the latest change indexed under data/high-availability
-    for each of the indexes. At startup scans all the changes, accounts and groups
-    and reindex the ones that have been updated by other nodes while the server was down.
-    When not specified, the default is "false", that means no automatic tracking
-    and indexing at start.
-
 ```peerInfo.strategy```
 :   Strategy to find other peers. Supported strategies are `static` or `jgroups`.
     Defaults to `jgroups`.
@@ -161,9 +160,6 @@
 :   The interval of time in milliseconds between the subsequent auto-retries.
     When not specified, the default value is set to 10000ms.
 
-NOTE: the default settings for `http.timeout` and `http.maxTries` ensure that
-the plugin will keep retrying to forward a message for one hour.
-
 ```cache.synchronize```
 :   Whether to synchronize cache evictions.
     Defaults to true.
@@ -216,6 +212,9 @@
 :   The interval of time in milliseconds between the subsequent auto-retries.
     Defaults to 30000 (30 seconds).
 
+NOTE: the default settings for `http.socketTimeout` and `http.maxTries` ensure
+that the plugin will keep retrying to forward a message for one hour.
+
 ```websession.synchronize```
 :   Whether to synchronize web sessions.
     Defaults to true.
diff --git a/src/test/docker/docker-compose.yaml b/src/test/docker/docker-compose.yaml
index 93971b3..647aa6d 100644
--- a/src/test/docker/docker-compose.yaml
+++ b/src/test/docker/docker-compose.yaml
@@ -2,17 +2,6 @@
 
 services:
 
-  postgres:
-    image: postgres:9.5.4
-    environment:
-      - POSTGRES_USER=gerrit
-      - POSTGRES_PASSWORD=secret
-      - POSTGRES_DB=reviewdb
-    networks:
-      - gerrit-net
-    volumes:
-      - ./pgdata:/var/lib/postgresql/data
-
   gerrit-01:
     build: gerrit
     ports:
@@ -20,8 +9,6 @@
       - "29411:29418"
     networks:
       - gerrit-net
-    depends_on:
-      - postgres
     volumes:
       - /dev/urandom:/dev/random
       - ./gitvolume:/var/gerrit/git
@@ -39,7 +26,6 @@
     networks:
       - gerrit-net
     depends_on:
-      - postgres
       - gerrit-01
     volumes:
       - /dev/urandom:/dev/random
diff --git a/src/test/docker/etc/gerrit.config b/src/test/docker/etc/gerrit.config
index c110835..90a4057 100644
--- a/src/test/docker/etc/gerrit.config
+++ b/src/test/docker/etc/gerrit.config
@@ -1,12 +1,7 @@
 [gerrit]
 	basePath = git
 	canonicalWebUrl = http://gerrit:8080/
-[database]
-	type = postgresql
-	hostname = postgres
-	database = reviewdb
-	username = gerrit
-	password = secret
+	serverId = f7696647-8efd-41b1-bd60-d321bc071ea9
 [index]
 	type = LUCENE
 [auth]
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile
index 537cb3c..707e5ae 100644
--- a/src/test/docker/gerrit/Dockerfile
+++ b/src/test/docker/gerrit/Dockerfile
@@ -1,19 +1,17 @@
-FROM gerritcodereview/gerrit:2.16.22
+FROM gerritcodereview/gerrit:3.0.12
 
-ENV GERRIT_BRANCH=stable-2.16
+ENV GERRIT_BRANCH=stable-3.0
 
 ENV GERRIT_CI_URL=https://gerrit-ci.gerritforge.com/job
 
 USER root
 
-RUN yum install -y iputils-ping netcat postgresql curl lsof gettext moreutils net-tools netcat inetutils-ping sudo
+RUN yum install -y iputils-ping netcat curl lsof gettext moreutils net-tools netcat inetutils-ping sudo
 
 USER gerrit
 
 ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody.jar /var/gerrit/plugins/javamelody.jar
-ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody-deps_deploy.jar /var/gerrit/lib/javamelody-deps_deploy.jar
 ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-high-availability-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/high-availability/high-availability.jar /var/gerrit/plugins/high-availability.jar
-ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-delete-project-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/delete-project/delete-project.jar /var/gerrit/plugins/delete-project.jar
 
 USER root
 
@@ -24,4 +22,6 @@
 
 ARG GERRIT_UID=1000
 RUN usermod -u ${GERRIT_UID} gerrit &> /dev/null
+
+ENTRYPOINT ["/usr/bin/env"]
 CMD /bin/start.sh
diff --git a/src/test/docker/gerrit/start.sh b/src/test/docker/gerrit/start.sh
index 7aa3550..0dae4c3 100755
--- a/src/test/docker/gerrit/start.sh
+++ b/src/test/docker/gerrit/start.sh
@@ -1,18 +1,14 @@
 #!/bin/bash -e
 
-wait-for-it.sh postgres:5432 -t 600 -- echo "Postgres is up"
-
 if [[ ! -z "$WAIT_FOR" ]]
 then
   wait-for-it.sh $WAIT_FOR -t 600 -- echo "$WAIT_FOR is up"
 fi
 
-chown -R gerrit: /var/gerrit
-
 sudo -u gerrit cp /var/gerrit/etc/gerrit.config.orig /var/gerrit/etc/gerrit.config
 sudo -u gerrit cp /var/gerrit/etc/high-availability.config.orig /var/gerrit/etc/high-availability.config
 
-if [[ ! -f /var/gerrit/git/All-Projects.git/config ]]
+if [[ ! -f /var/gerrit/etc/ssh_host_ed25519_key ]]
 then
   echo "Initializing Gerrit site ..."
   sudo -u gerrit java -jar /var/gerrit/bin/gerrit.war init -d /var/gerrit --batch
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index 5bca2cb..3444625 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -19,6 +19,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_NUM_STRIPED_LOCKS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY;
@@ -28,7 +29,6 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY;
@@ -115,15 +115,16 @@
 
   @Test
   public void testGetPeerInfoStrategy() {
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(DEFAULT_PEER_INFO_STRATEGY);
+    assertThat(getConfiguration().peerInfo().strategy())
+        .isSameInstanceAs(DEFAULT_PEER_INFO_STRATEGY);
 
     globalPluginConfig.setString(
         PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.STATIC.name());
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.STATIC);
+    assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.STATIC);
 
     globalPluginConfig.setString(
         PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.JGROUPS.name());
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.JGROUPS);
+    assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.JGROUPS);
   }
 
   @Test
@@ -132,7 +133,7 @@
 
     globalPluginConfig.setStringList(PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY, URLS);
     assertThat(getConfiguration().peerInfoStatic().urls())
-        .containsAllIn(ImmutableList.of(URL, "http://anotherUrl"));
+        .containsAtLeastElementsIn(ImmutableList.of(URL, "http://anotherUrl"));
   }
 
   @Test
@@ -156,7 +157,9 @@
 
     globalPluginConfig.setStringList(
         JGROUPS_SECTION, null, SKIP_INTERFACE_KEY, ImmutableList.of("lo*", "eth0"));
-    assertThat(getConfiguration().jgroups().skipInterface()).containsAllOf("lo*", "eth0").inOrder();
+    assertThat(getConfiguration().jgroups().skipInterface())
+        .containsExactly("lo*", "eth0")
+        .inOrder();
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
index 03c1928..9b6e66b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
@@ -20,6 +20,7 @@
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -76,4 +77,28 @@
       assertWithMessage(cache + " should not match").that(matcher.matches(cache)).isFalse();
     }
   }
+
+  @Test
+  public void testShouldNotMatchWebSessionsWhenNotSynchronized() {
+    String cache = "web_sessions";
+    when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList());
+    when(configurationMock.websession().synchronize()).thenReturn(false);
+    CachePatternMatcher matcher = new CachePatternMatcher(configurationMock);
+
+    assertWithMessage(cache + " should NOT match when websession.synchronize is false")
+        .that(matcher.matches(cache))
+        .isFalse();
+  }
+
+  @Test
+  public void testShouldMatchWebSessionsWhenSynchronized() {
+    String cache = "web_sessions";
+    when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList());
+    when(configurationMock.websession().synchronize()).thenReturn(true);
+    CachePatternMatcher matcher = new CachePatternMatcher(configurationMock);
+
+    assertWithMessage(cache + " should match when websession.synchronize is true")
+        .that(matcher.matches(cache))
+        .isTrue();
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java
new file mode 100644
index 0000000..2b9ee35
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.event;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.GsonProvider;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.RefUpdateAttribute;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gson.Gson;
+import org.junit.Test;
+
+public class EventDeserializerTest {
+  private static final String LEGACY_PROJECT_KEY = "{\"name\": \"project\"}";
+
+  private static final String NEW_PROJECT_KEY = "\"project\"";
+
+  private final Gson gson = new GsonProvider().get();
+
+  @Test
+  public void deserializePatchSetCreatedEventLegacyProjectKey() {
+    Project.NameKey n = gson.fromJson(LEGACY_PROJECT_KEY, Project.NameKey.class);
+    assertThat(n.get()).isEqualTo("project");
+  }
+
+  @Test
+  public void deserializePatchSetCreatedEventNewProjectKey() {
+    Project.NameKey n = gson.fromJson(NEW_PROJECT_KEY, Project.NameKey.class);
+    assertThat(n.get()).isEqualTo("project");
+  }
+
+  @Test
+  public void refUpdatedEventRoundTrip() {
+    RefUpdatedEvent refUpdatedEvent = new RefUpdatedEvent();
+
+    RefUpdateAttribute refUpdatedAttribute = new RefUpdateAttribute();
+    refUpdatedAttribute.refName = "refs/heads/master";
+    refUpdatedEvent.refUpdate = createSupplier(refUpdatedAttribute);
+
+    AccountAttribute accountAttribute = new AccountAttribute();
+    accountAttribute.email = "some.user@domain.com";
+    refUpdatedEvent.submitter = createSupplier(accountAttribute);
+
+    String serializedEvent = gson.toJson(refUpdatedEvent);
+    RefUpdatedEvent e = gson.fromJson(serializedEvent, RefUpdatedEvent.class);
+
+    assertThat(e).isNotNull();
+    assertThat(e.refUpdate).isInstanceOf(Supplier.class);
+    assertThat(e.refUpdate.get().refName).isEqualTo(refUpdatedAttribute.refName);
+    assertThat(e.submitter).isInstanceOf(Supplier.class);
+    assertThat(e.submitter.get().email).isEqualTo(accountAttribute.email);
+  }
+
+  private static <T> Supplier<T> createSupplier(T value) {
+    return Suppliers.memoize(() -> value);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
index ec5b1de..36cc953 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
@@ -39,7 +39,7 @@
     DynamicSet<EventListener> set = DynamicSet.emptySet();
     set.add("high-availability", listenerMock);
     PluginSetContext<EventListener> listeners = new PluginSetContext<>(set, mockMetrics);
-    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null, null);
+    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
index a855c5e..8ef8e2f 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
@@ -15,14 +15,14 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,14 +76,18 @@
             (Answer<Void>)
                 invocation -> {
                   assertThat(Context.isForwardedEvent()).isTrue();
-                  throw new OrmException("someMessage");
+                  throw new PermissionBackendException("someMessage");
                 })
         .when(dispatcherMock)
         .postEvent(event);
 
     assertThat(Context.isForwardedEvent()).isFalse();
-    OrmException thrown = assertThrows(OrmException.class, () -> handler.dispatch(event));
-    assertThat(thrown).hasMessageThat().contains("someMessage");
+    try {
+      handler.dispatch(event);
+      fail("should have throw a PermissionBackendException");
+    } catch (PermissionBackendException e) {
+      assertThat(e.getMessage()).isEqualTo("someMessage");
+    }
     assertThat(Context.isForwardedEvent()).isFalse();
 
     verify(dispatcherMock).postEvent(event);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
index b88e9c0..14f3d54 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.account.AccountIndexer;
@@ -38,16 +36,12 @@
 public class ForwardedIndexAccountHandlerTest {
 
   @Mock private AccountIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexAccountHandler handler;
   private Account.Id id;
 
   @Before
   public void setUp() throws Exception {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexAccountHandler(indexerMock, configMock);
+    handler = new ForwardedIndexAccountHandler(indexerMock);
     id = new Account.Id(123);
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
index 6bfd47d..a6da8e2 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -15,11 +15,9 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -28,14 +26,11 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl;
-import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
@@ -54,16 +49,10 @@
   private static String TEST_CHANGE_ID = TEST_PROJECT + "~" + TEST_CHANGE_NUMBER;
   private static final boolean CHANGE_EXISTS = true;
   private static final boolean CHANGE_DOES_NOT_EXIST = false;
-  private static final boolean DO_NOT_THROW_IO_EXCEPTION = false;
-  private static final boolean DO_NOT_THROW_ORM_EXCEPTION = false;
-  private static final boolean THROW_IO_EXCEPTION = true;
-  private static final boolean THROW_ORM_EXCEPTION = true;
   private static final boolean CHANGE_UP_TO_DATE = true;
   private static final boolean CHANGE_OUTDATED = false;
 
   @Mock private ChangeIndexer indexerMock;
-  @Mock private ChangeDb changeDbMock;
-  @Mock private ReviewDb dbMock;
   @Mock private ChangeNotes changeNotes;
   @Mock private Configuration configMock;
   @Mock private Configuration.Index indexMock;
@@ -77,35 +66,28 @@
 
   @Before
   public void setUp() throws Exception {
-    when(changeDbMock.open()).thenReturn(dbMock);
     id = new Change.Id(TEST_CHANGE_NUMBER);
     Change change = new Change(null, id, null, null, TimeUtil.nowTs());
     when(changeNotes.getChange()).thenReturn(change);
     when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
     when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerAbsentMock);
     handler =
         new ForwardedIndexChangeHandler(
-            indexerMock,
-            changeDbMock,
-            configMock,
-            indexExecutorMock,
-            ctxMock,
-            changeCheckerFactoryMock);
+            indexerMock, configMock, indexExecutorMock, ctxMock, changeCheckerFactoryMock);
   }
 
   @Test
   public void changeIsIndexedWhenUpToDate() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
   public void changeIsStillIndexedEvenWhenOutdated() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_OUTDATED);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.of(new IndexEvent()));
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
@@ -122,21 +104,6 @@
   }
 
   @Test
-  public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception {
-    setupChangeAccessRelatedMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION, CHANGE_UP_TO_DATE);
-    assertThrows(
-        OrmException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()));
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexChange() throws Exception {
-    setupChangeAccessRelatedMocks(
-        CHANGE_EXISTS, DO_NOT_THROW_ORM_EXCEPTION, THROW_IO_EXCEPTION, CHANGE_UP_TO_DATE);
-    assertThrows(
-        IOException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()));
-  }
-
-  @Test
   public void shouldSetAndUnsetForwardedContext() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE);
     // this doAnswer is to allow to assert that context is set to forwarded
@@ -148,13 +115,13 @@
                   return null;
                 })
         .when(indexerMock)
-        .index(any(ReviewDb.class), any(Change.class));
+        .index(any(Change.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
@@ -167,7 +134,7 @@
                   throw new IOException("someMessage");
                 })
         .when(indexerMock)
-        .index(any(ReviewDb.class), any(Change.class));
+        .index(any(Change.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     try {
@@ -178,39 +145,14 @@
     }
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
-  private void setupChangeAccessRelatedMocks(boolean changeExist, boolean changeUpToDate)
-      throws Exception {
-    setupChangeAccessRelatedMocks(
-        changeExist, DO_NOT_THROW_ORM_EXCEPTION, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate);
-  }
-
-  private void setupChangeAccessRelatedMocks(
-      boolean changeExist, boolean ormException, boolean changeUpToDate)
-      throws OrmException, IOException {
-    setupChangeAccessRelatedMocks(
-        changeExist, ormException, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate);
-  }
-
-  private void setupChangeAccessRelatedMocks(
-      boolean changeExists, boolean ormException, boolean ioException, boolean changeIsUpToDate)
-      throws OrmException, IOException {
-    if (ormException) {
-      doThrow(new OrmException("")).when(changeDbMock).open();
-    } else {
-      when(changeDbMock.open()).thenReturn(dbMock);
-    }
-
+  private void setupChangeAccessRelatedMocks(boolean changeExists, boolean changeIsUpToDate)
+      throws IOException {
     if (changeExists) {
       when(changeCheckerFactoryMock.create(TEST_CHANGE_ID)).thenReturn(changeCheckerPresentMock);
       when(changeCheckerPresentMock.getChangeNotes()).thenReturn(Optional.of(changeNotes));
-      if (ioException) {
-        doThrow(new IOException("io-error"))
-            .when(indexerMock)
-            .index(any(ReviewDb.class), any(Change.class));
-      }
     }
 
     when(changeCheckerPresentMock.isChangeUpToDate(any())).thenReturn(changeIsUpToDate);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
index 845abb0..7e30dea 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
@@ -38,16 +36,12 @@
 public class ForwardedIndexGroupHandlerTest {
 
   @Mock private GroupIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexGroupHandler handler;
   private AccountGroup.UUID uuid;
 
   @Before
   public void setUp() throws Exception {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexGroupHandler(indexerMock, configMock);
+    handler = new ForwardedIndexGroupHandler(indexerMock);
     uuid = new AccountGroup.UUID("123");
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
index a0c6979..ea36532 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.index.project.ProjectIndexer;
 import com.google.gerrit.reviewdb.client.Project;
@@ -38,16 +36,12 @@
 public class ForwardedIndexProjectHandlerTest {
 
   @Mock private ProjectIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexProjectHandler handler;
   private Project.NameKey nameKey;
 
   @Before
   public void setUp() {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexProjectHandler(indexerMock, configMock);
+    handler = new ForwardedIndexProjectHandler(indexerMock);
     nameKey = new Project.NameKey("project/name");
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
index 835977f..7872785 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.net.MediaType.JSON_UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 import static org.mockito.ArgumentMatchers.any;
@@ -29,7 +28,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.events.EventTypes;
 import com.google.gerrit.server.events.RefEvent;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
@@ -83,11 +82,11 @@
             + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
             + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
     when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event)));
-    doThrow(new OrmException(ERR_MSG))
+    doThrow(new PermissionBackendException(ERR_MSG))
         .when(forwardedEventHandlerMock)
         .dispatch(any(RefReplicationDoneEvent.class));
     eventRestApiServlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Change not found\n");
+    verify(responseMock).sendError(SC_BAD_REQUEST, ERR_MSG);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
index 5e0d4c9..4071363 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -28,6 +28,7 @@
 import com.github.tomakehurst.wiremock.http.Fault;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 import com.github.tomakehurst.wiremock.stubbing.Scenario;
+import com.google.gerrit.reviewdb.client.Project;
 import java.net.SocketTimeoutException;
 import org.junit.Before;
 import org.junit.Rule;
@@ -70,7 +71,7 @@
     when(configMock.http().socketTimeout()).thenReturn(TIMEOUT);
     when(configMock.http().retryInterval()).thenReturn(RETRY_INTERVAL);
 
-    httpSession = new HttpSession(new HttpClientProvider(configMock).get());
+    httpSession = new HttpSession(new HttpClientProvider(configMock).get(), new GsonProvider());
   }
 
   @Test
@@ -170,4 +171,12 @@
 
     assertThat(httpSession.post(uri).isSuccessful()).isFalse();
   }
+
+  @Test
+  public void encodeProjectName() {
+    String projectStr = "project";
+    Project.NameKey project = Project.nameKey(projectStr);
+    String json = httpSession.jsonEncode(project);
+    assertThat(json).isEqualTo("\"" + projectStr + "\"");
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
index c1fa765..fb3649e 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
@@ -15,7 +15,6 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -26,7 +25,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -81,15 +79,6 @@
   }
 
   @Test
-  public void indexerThrowsOrmExceptionTryingToIndexChange() throws Exception {
-    doThrow(new OrmException("some message"))
-        .when(handlerMock)
-        .index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Error trying to find change");
-  }
-
-  @Test
   public void sendErrorThrowsIOException() throws Exception {
     doThrow(new IOException(IO_ERROR))
         .when(handlerMock)
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
index 5bd0cf8..cb90461 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -37,6 +37,8 @@
 import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLException;
 import org.junit.Before;
 import org.junit.Test;
@@ -91,17 +93,22 @@
   private static final String EVENT_ENDPOINT =
       Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "event", event.type);
 
+  private static final long TEST_TIMEOUT = 10;
+  private static final TimeUnit TEST_TIMEOUT_UNITS = TimeUnit.SECONDS;
+
   private RestForwarder forwarder;
   private HttpSession httpSessionMock;
+  private Configuration configMock;
+  Provider<Set<PeerInfo>> peersMock;
 
   @SuppressWarnings("unchecked")
   @Before
   public void setUp() {
     httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
+    configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
     when(configMock.http().maxTries()).thenReturn(3);
     when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
+    peersMock = mock(Provider.class);
     when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
     forwarder =
         new RestForwarder(
@@ -109,126 +116,169 @@
             PLUGIN_NAME,
             configMock,
             peersMock,
-            gsonProvider); // TODO: Create provider
+            gsonProvider, // TODO: Create provider
+            new RestForwarderScheduler(Executors.newScheduledThreadPool(1)));
   }
 
   @Test
   public void testIndexAccountOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexAccountFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexAccountThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_ACCOUNT_ENDPOINT), any());
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexGroupOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isTrue();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexGroupFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexGroupThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_GROUP_ENDPOINT), any());
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexChangeOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexChangeFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexChangeThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_CHANGE_ENDPOINT), any());
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexBatchChangeOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isTrue();
   }
 
   @Test
   public void testIndexBatchChangeFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isFalse();
   }
 
   @Test
   public void testIndexBatchChangeThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any());
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isFalse();
   }
 
   @Test
   public void testChangeDeletedFromIndexOK() throws Exception {
     when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT)))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testChangeDeletedFromIndexFailed() throws Exception {
     when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT)))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testChangeDeletedFromThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).delete(eq(DELETE_CHANGE_ENDPOINT));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testEventSentOK() throws Exception {
     when(httpSessionMock.post(EVENT_ENDPOINT, event))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isTrue();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isTrue();
   }
 
   @Test
   public void testEventSentFailed() throws Exception {
     when(httpSessionMock.post(EVENT_ENDPOINT, event)).thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
   }
 
   @Test
   public void testEventSentThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT, event);
-    assertThat(forwarder.send(event)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
   }
 
   @Test
@@ -237,7 +287,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -246,7 +297,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.ACCOUNTS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -255,7 +307,8 @@
     String keyJson = gson.toJson(key);
     String endpoint = buildCacheEndpoint(Constants.GROUPS);
     when(httpSessionMock.post(endpoint, keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -264,7 +317,9 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS_BYINCLUDE, key)).isTrue();
+    assertThat(
+            forwarder.evict(Constants.GROUPS_BYINCLUDE, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -273,7 +328,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -282,7 +338,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -292,7 +349,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .post(buildCacheEndpoint(Constants.PROJECTS), keyJson);
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   private static String buildCacheEndpoint(String name) {
@@ -304,7 +362,8 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName)).isTrue();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -312,7 +371,8 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName)).isFalse();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -321,7 +381,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .post(buildProjectListCacheEndpoint(projectName), null);
-    assertThat(forwarder.addToProjectList(projectName)).isFalse();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -329,7 +390,8 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName)))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName)).isTrue();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -337,7 +399,8 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName)))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName)).isFalse();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -346,7 +409,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .delete((buildProjectListCacheEndpoint(projectName)));
-    assertThat(forwarder.removeFromProjectList(projectName)).isFalse();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   private static String buildProjectListCacheEndpoint(String projectName) {
@@ -354,41 +418,57 @@
   }
 
   @Test
-  public void testRetryOnErrorThenSuccess() throws IOException {
+  public void testRetryOnErrorThenSuccess() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
-  public void testRetryOnIoExceptionThenSuccess() throws IOException {
+  public void testRetryOnIoExceptionThenSuccess() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new IOException())
         .thenThrow(new IOException())
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
-  public void testNoRetryAfterNonRecoverableException() throws IOException {
+  public void testNoRetryAfterNonRecoverableException() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new SSLException("Non Recoverable"))
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
-  public void testFailureAfterMaxTries() throws IOException {
+  public void testFailureAfterMaxTries() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
index 4b2462e..a66e896 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
@@ -26,11 +26,11 @@
 
   @Override
   public String getExpectedRequest() {
-    return "/plugins/high-availability/index/account/" + testAccount.id;
+    return "/plugins/high-availability/index/account/" + testAccount.id();
   }
 
   @Override
   public void doAction() throws Exception {
-    gApi.accounts().id(testAccount.id.get()).setActive(false);
+    gApi.accounts().id(testAccount.id().get()).setActive(false);
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
similarity index 90%
rename from src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
index b691034..bc5210d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
@@ -21,28 +21,20 @@
 import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 @TestPlugin(
     name = "high-availability",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
-public class ChangeCheckerNoteDbIT extends LightweightPluginDaemonTest {
+public class ChangeCheckerIT extends LightweightPluginDaemonTest {
 
   ChangeCheckerImpl.Factory changeCheckerFactory;
 
-  @BeforeClass
-  public static void setupTestSuite() {
-    System.setProperty("gerrit.notedb", NoteDbMode.ON.name());
-  }
-
   @Override
   public void setUpTestPlugin() throws Exception {
     super.setUpTestPlugin();
@@ -50,7 +42,7 @@
   }
 
   @Test
-  public void shouldPopulateMetaShaWhenNoteDb() throws Exception {
+  public void shouldPopulateMetaSha() throws Exception {
     Result change = createChange();
     ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
     Optional<IndexEvent> eventOption = changeChecker.newIndexEvent();
@@ -121,7 +113,7 @@
     assertThat(changeChecker.isChangeUpToDate(event)).isFalse();
   }
 
-  private String readMetaSha(Result change) throws IOException, OrmException {
+  private String readMetaSha(Result change) throws IOException {
     try (Repository repo = repoManager.openRepository(change.getChange().change().getProject())) {
       String refName = RefNames.changeMetaRef(change.getChange().getId());
       Ref ref = repo.exactRef(refName);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java
deleted file mode 100644
index a6cb449..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.ericsson.gerrit.plugins.highavailability.index;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit.Result;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.testing.NoteDbMode;
-import java.util.Optional;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-@TestPlugin(
-    name = "high-availability",
-    sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
-    httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
-public class ChangeCheckerReviewDbIT extends LightweightPluginDaemonTest {
-
-  ChangeCheckerImpl.Factory changeCheckerFactory;
-
-  @BeforeClass
-  public static void setupTestSuite() {
-    System.setProperty("gerrit.notedb", NoteDbMode.OFF.name());
-  }
-
-  @Override
-  public void setUpTestPlugin() throws Exception {
-    super.setUpTestPlugin();
-    changeCheckerFactory = plugin.getSysInjector().getInstance(ChangeCheckerImpl.Factory.class);
-  }
-
-  @Test
-  public void shouldNotPopulateMetaShaWhenReviewDb() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> eventOption = changeChecker.newIndexEvent();
-
-    assertThat(eventOption.isPresent()).isTrue();
-    IndexEvent event = eventOption.get();
-    assertThat(event.metaSha).isNull();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenEventContainsCorrectMetaAndTargetSha()
-      throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event = changeChecker.newIndexEvent();
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenMetaShaIsNull() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.metaSha = null;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenTargetShaIsNull() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.targetSha = null;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnFalseWhenTargetShaIsNotUpToDate() throws Exception {
-    String testTargetRefSha = "abed47baf2818a86b68cf712073a748a6b5b293e";
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.targetSha = testTargetRefSha;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isFalse();
-  }
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
index 4c20ae1..5f08cc5 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,20 +33,37 @@
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexAccountTask;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexChangeTask;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexGroupTask;
-import com.google.common.util.concurrent.MoreExecutors;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventLocks.VoidFunction;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -56,6 +74,8 @@
   private static final int ACCOUNT_ID = 2;
   private static final String UUID = "3";
   private static final String OTHER_UUID = "4";
+  private static final Integer INDEX_WAIT_TIMEOUT_MS = 5;
+  private static final int MAX_TEST_PARALLELISM = 4;
 
   private IndexEventHandler indexEventHandler;
   @Mock private Forwarder forwarder;
@@ -64,7 +84,13 @@
   private Change.Id changeId;
   private Account.Id accountId;
   private AccountGroup.UUID accountGroupUUID;
+  private ScheduledExecutorService executor = new CurrentThreadScheduledExecutorService();
+  private ScheduledExecutorService batchExecutor = new CurrentThreadScheduledExecutorService();
+  private ScheduledExecutorService testExecutor =
+      Executors.newScheduledThreadPool(MAX_TEST_PARALLELISM);
   @Mock private RequestContext mockCtx;
+  @Mock private Configuration configuration;
+  private IndexEventLocks idLocks;
 
   private CurrentRequestContext currCtx =
       new CurrentRequestContext(null, null, null) {
@@ -82,18 +108,48 @@
     when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerMock);
     when(changeCheckerMock.newIndexEvent()).thenReturn(Optional.of(new IndexEvent()));
 
+    Configuration.Index cfgIndex = mock(Configuration.Index.class);
+    when(configuration.index()).thenReturn(cfgIndex);
+    when(cfgIndex.numStripedLocks()).thenReturn(Configuration.DEFAULT_NUM_STRIPED_LOCKS);
+
+    Configuration.Http http = mock(Configuration.Http.class);
+    when(configuration.http()).thenReturn(http);
+    when(http.maxTries()).thenReturn(Configuration.Http.DEFAULT_MAX_TRIES);
+    when(http.retryInterval()).thenReturn(Configuration.Http.DEFAULT_RETRY_INTERVAL);
+    when(forwarder.indexAccount(eq(ACCOUNT_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.indexGroup(eq(UUID), any())).thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+
+    idLocks = new IndexEventLocks(configuration);
     setUpIndexEventHandler(currCtx);
   }
 
   public void setUpIndexEventHandler(CurrentRequestContext currCtx) throws Exception {
+    setUpIndexEventHandler(currCtx, idLocks, configuration);
+  }
+
+  public void setUpIndexEventHandler(CurrentRequestContext currCtx, IndexEventLocks idLocks)
+      throws Exception {
+    setUpIndexEventHandler(currCtx, idLocks, configuration);
+  }
+
+  public void setUpIndexEventHandler(
+      CurrentRequestContext currCtx, IndexEventLocks idLocks, Configuration configuration)
+      throws Exception {
     indexEventHandler =
         new IndexEventHandler(
-            MoreExecutors.directExecutor(),
-            MoreExecutors.directExecutor(),
+            executor,
+            batchExecutor,
             PLUGIN_NAME,
             forwarder,
             changeCheckerFactoryMock,
-            currCtx);
+            currCtx,
+            configuration,
+            idLocks);
   }
 
   @Test
@@ -116,6 +172,160 @@
   }
 
   @Test
+  public void shouldNotIndexChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotIndexAccountWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onAccountIndexed(accountId.get());
+
+    verify(forwarder, never()).indexAccount(eq(ACCOUNT_ID), any());
+  }
+
+  @Test
+  public void shouldNotDeleteChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeDeleted(changeId.get());
+
+    verify(forwarder, never()).deleteChangeFromIndex(eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotIndexGroupWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
+
+    verify(forwarder, never()).indexGroup(eq(UUID), any());
+  }
+
+  @Test
+  public void shouldNotIndexProjectWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onProjectIndexed(PROJECT_NAME);
+
+    verify(forwarder, never()).indexProject(eq(PROJECT_NAME), any());
+  }
+
+  @Test
+  public void shouldRetryIndexChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false, true);
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(2)).withLock(any(), any(), any());
+    verify(forwarder, times(1)).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldRetryUpToMaxTriesWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+
+    Configuration cfg = mock(Configuration.class);
+    Configuration.Http httpCfg = mock(Configuration.Http.class);
+    when(httpCfg.maxTries()).thenReturn(10);
+    when(cfg.http()).thenReturn(httpCfg);
+    setUpIndexEventHandler(currCtx, locks, cfg);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(11)).withLock(any(), any(), any());
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotRetryWhenMaxTriesLowerThanOne() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+
+    Configuration cfg = mock(Configuration.class);
+    Configuration.Http httpCfg = mock(Configuration.Http.class);
+    when(httpCfg.maxTries()).thenReturn(0);
+    when(cfg.http()).thenReturn(httpCfg);
+    setUpIndexEventHandler(currCtx, locks, cfg);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(1)).withLock(any(), any(), any());
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldLockPerIndexEventType() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore indexChangeLock = mock(Semaphore.class);
+    when(indexChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Semaphore accountChangeLock = mock(Semaphore.class);
+    when(accountChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(true);
+    when(locks.getSemaphore(eq("change/" + CHANGE_ID))).thenReturn(indexChangeLock);
+    when(locks.getSemaphore(eq("account/" + ACCOUNT_ID))).thenReturn(accountChangeLock);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+    indexEventHandler.onAccountIndexed(accountId.get());
+
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+    verify(forwarder).indexAccount(eq(ACCOUNT_ID), any());
+  }
+
+  @Test
   public void shouldReindexInRemoteWhenContextIsMissingButForcedIndexingEnabled() throws Exception {
     ThreadLocalRequestContext threadLocalCtxMock = mock(ThreadLocalRequestContext.class);
     OneOffRequestContext oneOffCtxMock = mock(OneOffRequestContext.class);
@@ -182,7 +392,14 @@
     ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
         new IndexEventHandler(
-            poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+            poolMock,
+            poolBatchMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
     indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
     verify(poolMock, times(1))
@@ -195,7 +412,14 @@
     ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
         new IndexEventHandler(
-            poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+            poolMock,
+            poolBatchMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onAccountIndexed(accountId.get());
     indexEventHandler.onAccountIndexed(accountId.get());
     verify(poolMock, times(1)).execute(indexEventHandler.new IndexAccountTask(ACCOUNT_ID));
@@ -207,7 +431,14 @@
     ScheduledThreadPoolExecutor poolBatchMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
         new IndexEventHandler(
-            poolMock, poolBatchMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+            poolMock,
+            poolBatchMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onGroupIndexed(accountGroupUUID.get());
     indexEventHandler.onGroupIndexed(accountGroupUUID.get());
     verify(poolMock, times(1)).execute(indexEventHandler.new IndexGroupTask(UUID));
@@ -323,4 +554,226 @@
     assertThat(task.equals(differentGroupIdTask)).isFalse();
     assertThat(task.hashCode()).isNotEqualTo(differentGroupIdTask.hashCode());
   }
+
+  class TestTask<T> implements Runnable {
+    private IndexTask task;
+    private CyclicBarrier testBarrier;
+    private Supplier<T> successFunc;
+    private VoidFunction failureFunc;
+    private CompletableFuture<T> future;
+
+    public TestTask(
+        IndexTask task,
+        CyclicBarrier testBarrier,
+        Supplier<T> successFunc,
+        VoidFunction failureFunc) {
+      this.task = task;
+      this.testBarrier = testBarrier;
+      this.successFunc = successFunc;
+      this.failureFunc = failureFunc;
+      this.future = new CompletableFuture<>();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void run() {
+      try {
+        testBarrier.await();
+        idLocks
+            .withLock(
+                task,
+                () ->
+                    runLater(
+                        INDEX_WAIT_TIMEOUT_MS * 2,
+                        () -> CompletableFuture.completedFuture(successFunc.get())),
+                failureFunc)
+            .whenComplete(
+                (v, t) -> {
+                  if (t == null) {
+                    future.complete((T) v);
+                  } else {
+                    future.completeExceptionally(t);
+                  }
+                });
+      } catch (Throwable t) {
+        future = new CompletableFuture<>();
+        future.completeExceptionally(t);
+      }
+    }
+
+    public void join() {
+      try {
+        future.join();
+      } catch (Exception e) {
+      }
+    }
+
+    private CompletableFuture<T> runLater(
+        long scheduledTimeMsec, Supplier<CompletableFuture<T>> supplier) {
+      CompletableFuture<T> resFuture = new CompletableFuture<>();
+      testExecutor.schedule(
+          () -> {
+            try {
+              return supplier
+                  .get()
+                  .whenComplete(
+                      (v, t) -> {
+                        if (t == null) {
+                          resFuture.complete(v);
+                        }
+                        resFuture.completeExceptionally(t);
+                      });
+            } catch (Throwable t) {
+              return resFuture.completeExceptionally(t);
+            }
+          },
+          scheduledTimeMsec,
+          TimeUnit.MILLISECONDS);
+      return resFuture;
+    }
+  }
+
+  @Test
+  public void indexLocksShouldBlockConcurrentIndexChange() throws Exception {
+    IndexChangeTask indexTask1 =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    IndexChangeTask indexTask2 =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    testIsolationOfCuncurrentIndexTasks(indexTask1, indexTask2);
+  }
+
+  @Test
+  public void indexLocksShouldBlockConcurrentIndexAndDeleteChange() throws Exception {
+    IndexChangeTask indexTask =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    DeleteChangeTask deleteTask =
+        indexEventHandler.new DeleteChangeTask(CHANGE_ID, new IndexEvent());
+    testIsolationOfCuncurrentIndexTasks(indexTask, deleteTask);
+  }
+
+  private void testIsolationOfCuncurrentIndexTasks(IndexTask indexTask1, IndexTask indexTask2)
+      throws Exception {
+    AtomicInteger changeIndexedCount = new AtomicInteger();
+    AtomicInteger lockFailedCounts = new AtomicInteger();
+    CyclicBarrier changeThreadsSync = new CyclicBarrier(2);
+
+    TestTask<Integer> task1 =
+        new TestTask<>(
+            indexTask1,
+            changeThreadsSync,
+            () -> changeIndexedCount.incrementAndGet(),
+            () -> lockFailedCounts.incrementAndGet());
+    TestTask<Integer> task2 =
+        new TestTask<>(
+            indexTask2,
+            changeThreadsSync,
+            () -> changeIndexedCount.incrementAndGet(),
+            () -> lockFailedCounts.incrementAndGet());
+
+    new Thread(task1).start();
+    new Thread(task2).start();
+    task1.join();
+    task2.join();
+
+    /* Both assertions needs to be true, the order doesn't really matter:
+     * - Only one of the two tasks should succeed
+     * - Only one of the two tasks should fail to acquire the lock
+     */
+    assertThat(changeIndexedCount.get()).isEqualTo(1);
+    assertThat(lockFailedCounts.get()).isEqualTo(1);
+  }
+
+  private class CurrentThreadScheduledExecutorService implements ScheduledExecutorService {
+
+    @Override
+    public void shutdown() {}
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return false;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return false;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      return null;
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      return null;
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException, ExecutionException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException, ExecutionException, TimeoutException {
+      return null;
+    }
+
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+      command.run();
+      return null;
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(
+        Runnable command, long initialDelay, long period, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(
+        Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      return null;
+    }
+  }
 }
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
index 281515d..43ee6ec 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects",
     "entries": "PROJECTS_ENTRIES"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
index 3fd506d..f128e73 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/_PROJECT",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/_PROJECT",
     "cmd": "clone"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
index b535c1d..a167922 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/changes/",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/changes/",
     "project": "_PROJECT"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
index da3f028..2e5073b 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/projects/PROJECT"
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/projects/PROJECT"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
index 8e0a304..4ecbe68 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/a/changes/",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/changes/",
     "number": "NUMBER"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
index be47699..2405b03 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME/a/projects/PROJECT/delete-project~delete"
+    "url": "HTTP_SCHEME://HOSTNAME/a/projects/PROJECT/delete-project~delete"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
index c938973..ae904f5 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush"
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
index f7450a4..25521c7 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME/a/config/server/caches/projects"
+    "url": "HTTP_SCHEME://HOSTNAME/a/config/server/caches/projects"
   }
 ]
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
index 10374f8..6a0d07f 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
@@ -32,7 +32,7 @@
     this.producer = Some(producer)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(session => {
       if (producer.nonEmpty) {
@@ -41,12 +41,12 @@
         session
       }
     })
-    .exec(http(unique).get("${url}")
+    .exec(http(uniqueName).get("${url}")
       .check(regex("\"" + memKey + "\": (\\d+)")
         .is(session => session(entriesKey).as[String])))
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
index 13a741a..4fa0ca5 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
@@ -23,37 +23,37 @@
 
 class CloneUsingHAGerrit2 extends GitSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private var default: String = name
+  private var projectName = className
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
   override def replaceOverride(in: String): String = {
     val next = replaceProperty("http_port2", 8082, in)
-    replaceKeyWith("_project", default, next)
+    replaceKeyWith("_project", projectName, next)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(gitRequest)
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
-    ),
+      atOnceUsers(single)
+    ).protocols(gitProtocol),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
-  ).protocols(gitProtocol, httpProtocol)
+  ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
index f07aabd..26732b6 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
@@ -24,7 +24,7 @@
 
 class CreateChangeUsingHAGerrit1 extends GerritSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private val default: String = name
+  private val projectName = className
   private val numberKey = "_number"
 
   override def relativeRuntimeWeight = 10
@@ -33,7 +33,7 @@
     replaceProperty("http_port1", 8081, in)
   }
 
-  private val test: ScenarioBuilder = scenario(unique)
+  private val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest
       .body(ElFileBody(body)).asJson
@@ -43,26 +43,26 @@
       session
     })
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
   private val deleteChange = new DeleteChangeUsingHAGerrit2
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteChange.test.inject(
       nothingFor(stepWaitTime(deleteChange) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
index 964aadb..5773bcf 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
@@ -22,9 +22,9 @@
 class CreateProjectUsingHAGerrit1 extends ProjectSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
   override def replaceOverride(in: String): String = {
@@ -32,12 +32,12 @@
     super.replaceOverride(next)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest.body(RawFileBody(body)).asJson)
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
index a4da93c..fd83e7a 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
@@ -20,34 +20,34 @@
 import scala.concurrent.duration._
 
 class CreateProjectUsingHAGerritTwice extends GitSimulation {
-  private val default: String = name
+  private val projectName = className
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
-  private val createItAgain = new CreateProjectUsingHAGerrit1(default)
-  private val verifyProject = new CloneUsingHAGerrit2(default)
-  private val deleteItAfter = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
+  private val createItAgain = new CreateProjectUsingHAGerrit1(projectName)
+  private val verifyProject = new CloneUsingHAGerrit2(projectName)
+  private val deleteItAfter = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     createItAgain.test.inject(
       nothingFor(stepWaitTime(createItAgain) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     verifyProject.test.inject(
       nothingFor(stepWaitTime(verifyProject) seconds),
-      atOnceUsers(1)
-    ),
+      atOnceUsers(single)
+    ).protocols(gitProtocol),
     deleteItAfter.test.inject(
       nothingFor(stepWaitTime(deleteItAfter) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
-  ).protocols(gitProtocol, httpProtocol)
+  ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
index 5b0f461..d3dc7c1 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
@@ -30,7 +30,7 @@
     replaceProperty("http_port2", 8082, in)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(session => {
       if (number.nonEmpty) {
@@ -39,10 +39,10 @@
         session
       }
     })
-    .exec(http(unique).delete("${url}${number}"))
+    .exec(http(uniqueName).delete("${url}${number}"))
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
index 428085d..cdc6e9e 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
@@ -22,17 +22,17 @@
 class DeleteProjectUsingHAGerrit extends ProjectSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest)
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
index 9c317d4..4f619b8 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
@@ -23,7 +23,7 @@
 
 class FlushProjectsCacheUsingHAGerrit2 extends CacheFlushSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private val default: String = name
+  private val projectName = className
 
   override def relativeRuntimeWeight = 2
 
@@ -31,35 +31,35 @@
     replaceProperty("http_port2", 8082, in)
   }
 
-  private val flushCache: ScenarioBuilder = scenario(unique)
+  private val flushCache: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest)
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
   private val getCacheEntriesAfterProject = new GetProjectsCacheEntries(this)
   private val checkCacheEntriesAfterFlush = new CheckProjectsCacheFlushEntriesUsingHAGerrit1(this)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     getCacheEntriesAfterProject.test.inject(
       nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     flushCache.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     checkCacheEntriesAfterFlush.test.inject(
       nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
index 9565365..ed19c0b 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
@@ -28,9 +28,9 @@
     this.consumer = Some(consumer)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
-    .exec(http(unique).get("${url}")
+    .exec(http(uniqueName).get("${url}")
       .check(regex("\"" + memKey + "\": (\\d+)").saveAs(entriesKey)))
     .exec(session => {
       if (consumer.nonEmpty) {
@@ -41,6 +41,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }