Add metric displaying the number of retries required to forward event

Change-Id: I2bc7b3f7b942afa46c9d3abf2d8fc6033aa7a0b1
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 3854ee3..5d92700 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
@@ -21,6 +21,34 @@
 /** Forward indexing, stream events and cache evictions to the other primary */
 public interface Forwarder {
 
+  public class Result {
+    private EventType type;
+    private boolean result;
+    private boolean isRecoverable;
+
+    public Result(EventType task, boolean result) {
+      this(task, result, true);
+    }
+
+    public Result(EventType type, boolean result, boolean isRecoverable) {
+      this.type = type;
+      this.result = result;
+      this.isRecoverable = isRecoverable;
+    }
+
+    public EventType getType() {
+      return type;
+    }
+
+    public boolean getResult() {
+      return result;
+    }
+
+    public boolean isRecoverable() {
+      return isRecoverable;
+    }
+  }
+
   /**
    * Forward an account indexing event to the other primary.
    *
@@ -29,7 +57,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent);
+  CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other primary.
@@ -40,7 +68,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> indexChange(String projectName, int changeId, IndexEvent indexEvent);
+  CompletableFuture<Result> indexChange(String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other primary using batch index endpoint.
@@ -51,7 +79,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> batchIndexChange(
+  CompletableFuture<Result> batchIndexChange(
       String projectName, int changeId, IndexEvent indexEvent);
 
   /**
@@ -62,7 +90,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
+  CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a group indexing event to the other primary.
@@ -72,7 +100,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent);
+  CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent);
 
   /**
    * Forward a project indexing event to the other primary.
@@ -82,7 +110,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent);
+  CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent);
 
   /**
    * Forward a stream event to the other primary.
@@ -91,7 +119,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> send(Event event);
+  CompletableFuture<Result> send(Event event);
 
   /**
    * Forward a cache eviction event to the other primary.
@@ -101,7 +129,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> evict(String cacheName, Object key);
+  CompletableFuture<Result> evict(String cacheName, Object key);
 
   /**
    * Forward an addition to the project list cache to the other primary.
@@ -110,7 +138,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> addToProjectList(String projectName);
+  CompletableFuture<Result> addToProjectList(String projectName);
 
   /**
    * Forward a removal from the project list cache to the other primary.
@@ -119,7 +147,7 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> removeFromProjectList(String projectName);
+  CompletableFuture<Result> removeFromProjectList(String projectName);
 
   /**
    * Forward the removal of all project changes from index to the other primary.
@@ -128,5 +156,5 @@
    * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
    *     false.
    */
-  CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName);
+  CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName);
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java
index 922e62b..eecef15 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.metrics.Counter0;
 import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Histogram0;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.metrics.Timer0;
 import com.google.inject.assistedinject.Assisted;
@@ -27,6 +28,7 @@
   private final Timer0 latencyMetric;
   private final Counter0 failureCounterMetric;
   private final Counter0 successCounterMetric;
+  private final Histogram0 retryMetric;
 
   public interface Factory {
     ForwarderMetrics create(EventType eventType);
@@ -56,6 +58,11 @@
             new Description(String.format("%s events forwarding success count", event))
                 .setCumulative()
                 .setRate());
+    this.retryMetric =
+        metricMaker.newHistogram(
+            String.format("forwarding_%s_event/retries", eventType),
+            new Description(String.format("%s events forwarding retries", eventType))
+                .setCumulative());
   }
 
   public void recordResult(boolean isSuccessful) {
@@ -69,4 +76,8 @@
   public void recordLatency(long latencyMs) {
     latencyMetric.record(latencyMs, TimeUnit.MILLISECONDS);
   }
+
+  public void recordRetries(int retries) {
+    retryMetric.record(retries);
+  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
index 5acb305..f5c5a39 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
@@ -26,6 +26,7 @@
     bind(AllowedForwardedEventListener.class)
         .to(ConfigurableAllowedEventListeners.class)
         .in(Scopes.SINGLETON);
+    bind(ForwarderMetricsRegistry.class);
     DynamicItem.bind(binder(), EventDispatcher.class).to(ForwardedAwareEventBroker.class);
     factory(ForwarderMetrics.Factory.class);
     bind(ForwarderMetricsRegistry.class).in(Scopes.SINGLETON);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java
new file mode 100644
index 0000000..f8895e1
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2025 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;
+
+public enum ForwarderTask {
+  ADD_TO_PROJECTS_LIST,
+  BATCH_INDEX_CHANGE,
+  DELETE_CHANGE_FROM_INDEX,
+  EVICT_CACHE,
+  INDEX_ACCOUNT,
+  INDEX_CHANGE,
+  INDEX_GROUP,
+  INDEX_PROJECT,
+  REMOVE_FROM_PROJECTS_LIST,
+  SEND_EVENT
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java
index 8f81832..df2ab14 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
@@ -23,7 +24,7 @@
 import dev.failsafe.FailsafeExecutor;
 import dev.failsafe.RetryPolicy;
 
-public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Boolean>> {
+public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Result>> {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final Configuration cfg;
   private final WorkQueue workQueue;
@@ -35,9 +36,9 @@
   }
 
   @Override
-  public FailsafeExecutor<Boolean> get() {
-    RetryPolicy<Boolean> retryPolicy =
-        RetryPolicy.<Boolean>builder()
+  public FailsafeExecutor<Result> get() {
+    RetryPolicy<Result> retryPolicy =
+        RetryPolicy.<Result>builder()
             .withMaxAttempts(cfg.jgroups().maxTries())
             .withDelay(cfg.jgroups().retryInterval())
             .onRetry(e -> log.atFine().log("Retrying event %s", e))
@@ -45,7 +46,7 @@
                 e ->
                     log.atWarning().log(
                         "%d jgroups retries exceeded for event %s", cfg.jgroups().maxTries(), e))
-            .handleResult(false)
+            .handleResultIf(r -> !r.getResult())
             .build();
     return Failsafe.with(retryPolicy)
         .with(workQueue.createQueue(cfg.jgroups().threadPoolSize(), "JGroupsForwarder"));
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
index 90d70c9..be8bd7c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
@@ -46,7 +46,7 @@
   private final MessageDispatcher dispatcher;
   private final JGroups jgroupsConfig;
   private final Gson gson;
-  private final FailsafeExecutor<Boolean> executor;
+  private final FailsafeExecutor<Result> executor;
   private final ForwarderMetricsRegistry metricsRegistry;
 
   @Inject
@@ -54,7 +54,7 @@
       MessageDispatcher dispatcher,
       Configuration cfg,
       @JGroupsGson Gson gson,
-      @JGroupsForwarderExecutor FailsafeExecutor<Boolean> executor,
+      @JGroupsForwarderExecutor FailsafeExecutor<Result> executor,
       ForwarderMetricsRegistry metricsRegistry) {
     this.dispatcher = dispatcher;
     this.jgroupsConfig = cfg.jgroups();
@@ -62,71 +62,75 @@
     this.executor = executor;
 
     this.metricsRegistry = metricsRegistry;
+    this.executor.onComplete(
+        ev -> {
+          this.metricsRegistry.get(ev.getResult().getType()).recordRetries(ev.getAttemptCount());
+        });
   }
 
   @Override
-  public CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent) {
+  public CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent) {
     return execute(new IndexAccount(accountId, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> indexChange(
+  public CompletableFuture<Result> indexChange(
       String projectName, int changeId, IndexEvent indexEvent) {
     return execute(new IndexChange.Update(projectName, changeId, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> batchIndexChange(
+  public CompletableFuture<Result> batchIndexChange(
       String projectName, int changeId, IndexEvent indexEvent) {
     return execute(new IndexChange.BatchUpdate(projectName, changeId, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent) {
+  public CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent) {
     return execute(new IndexChange.Delete(changeId, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent) {
+  public CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent) {
     return execute(new IndexGroup(uuid, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent) {
+  public CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent) {
     return execute(new IndexProject(projectName, indexEvent.eventCreatedOn));
   }
 
   @Override
-  public CompletableFuture<Boolean> send(Event event) {
+  public CompletableFuture<Result> send(Event event) {
     return execute(new PostEvent(event, Instant.ofEpochSecond(event.eventCreatedOn)));
   }
 
   @Override
-  public CompletableFuture<Boolean> evict(String cacheName, Object key) {
+  public CompletableFuture<Result> evict(String cacheName, Object key) {
     return execute(new EvictCache(cacheName, gson.toJson(key), Instant.now()));
   }
 
   @Override
-  public CompletableFuture<Boolean> addToProjectList(String projectName) {
+  public CompletableFuture<Result> addToProjectList(String projectName) {
     return execute(new AddToProjectList(projectName, Instant.now()));
   }
 
   @Override
-  public CompletableFuture<Boolean> removeFromProjectList(String projectName) {
+  public CompletableFuture<Result> removeFromProjectList(String projectName) {
     return execute(new RemoveFromProjectList(projectName, Instant.now()));
   }
 
   @Override
-  public CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName) {
+  public CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName) {
     return execute(new DeleteAllProjectChangesFromIndex(projectName, Instant.now()));
   }
 
-  private CompletableFuture<Boolean> execute(Command cmd) {
+  private CompletableFuture<Result> execute(Command cmd) {
     return executor
         .getAsync(() -> executeOnce(cmd))
         .thenApplyAsync(
             result -> {
-              metricsRegistry.get(cmd.type).recordResult(result);
+              metricsRegistry.get(cmd.type).recordResult(result.getResult());
               metricsRegistry
                   .get(cmd.type)
                   .recordLatency(Duration.between(cmd.eventCreatedOn, Instant.now()).toMillis());
@@ -134,14 +138,14 @@
             });
   }
 
-  private boolean executeOnce(Command cmd) {
+  private Result executeOnce(Command cmd) {
     String json = gson.toJson(cmd);
     try {
       logJGroupsInfo();
 
       if (dispatcher.getChannel().getView().size() < 2) {
         log.atFine().log("Less than two members in cluster, not sending %s", json);
-        return false;
+        return new Result(cmd.type, false);
       }
 
       log.atFine().log("Sending %s", json);
@@ -151,7 +155,7 @@
 
       log.atFine().log("Received response list length = %s", list.size());
       if (list.isEmpty()) {
-        return false;
+        return new Result(cmd.type, false);
       }
 
       for (Entry<Address, Rsp<Object>> e : list.entrySet()) {
@@ -160,14 +164,14 @@
           log.atWarning().log(
               "Received a non TRUE response from receiver %s: %s",
               e.getKey(), e.getValue().getValue());
-          return false;
+          return new Result(cmd.type, false);
         }
       }
       log.atFine().log("Successfully sent message %s", json);
-      return true;
+      return new Result(cmd.type, true);
     } catch (Exception e) {
       log.atWarning().withCause(e).log("Forwarding %s failed", json);
-      return false;
+      return new Result(cmd.type, false);
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
index dee4a6d..360ba40 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.ericsson.gerrit.plugins.highavailability.peers.jgroups.JChannelProviderModule;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.events.EventGson;
@@ -38,7 +39,7 @@
     install(new JChannelProviderModule());
     listener().to(OnStartStop.class);
 
-    bind(new TypeLiteral<FailsafeExecutor<Boolean>>() {})
+    bind(new TypeLiteral<FailsafeExecutor<Result>>() {})
         .annotatedWith(JGroupsForwarderExecutor.class)
         .toProvider(FailsafeExecutorProvider.class)
         .in(Scopes.SINGLETON);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java
index 8134220..6c27d4b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java
@@ -15,18 +15,18 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import dev.failsafe.Failsafe;
 import dev.failsafe.FailsafeExecutor;
-import dev.failsafe.Fallback;
 import dev.failsafe.RetryPolicy;
 import java.util.concurrent.Executors;
 
 @Singleton
-public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Boolean>> {
+public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Result>> {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final Configuration cfg;
 
@@ -36,10 +36,9 @@
   }
 
   @Override
-  public FailsafeExecutor<Boolean> get() {
-    Fallback<Boolean> fallbackToFalse = Fallback.<Boolean>of(() -> false);
-    RetryPolicy<Boolean> retryPolicy =
-        RetryPolicy.<Boolean>builder()
+  public FailsafeExecutor<Result> get() {
+    RetryPolicy<Result> retryPolicy =
+        RetryPolicy.<Result>builder()
             .withMaxAttempts(cfg.http().maxTries())
             .withDelay(cfg.http().retryInterval())
             .onRetry(e -> log.atFine().log("Retrying event %s", e))
@@ -47,15 +46,13 @@
                 e ->
                     log.atWarning().log(
                         "%d http retries exceeded for event %s", cfg.http().maxTries(), e))
-            .handleResult(false)
-            .abortIf(
-                (r, e) ->
-                    e instanceof ForwardingException && !((ForwardingException) e).isRecoverable())
+            .handleResultIf(r -> !r.getResult())
+            .abortIf((r, e) -> !r.getResult() && !r.isRecoverable())
             .build();
     // TODO: the executor shall be created by workQueue.createQueue(...)
     //   However, this currently doesn't work because WorkQueue.Executor doesn't support wrapping of
     //   Callable i.e. it throws an exception on decorateTask(Callable)
-    return Failsafe.with(fallbackToFalse, retryPolicy)
+    return Failsafe.with(retryPolicy)
         .with(Executors.newScheduledThreadPool(cfg.http().threadPoolSize()));
   }
 }
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 b736185..6d15755 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
@@ -58,7 +58,7 @@
   private final Configuration cfg;
   private final Provider<Set<PeerInfo>> peerInfoProvider;
   private final Gson gson;
-  private FailsafeExecutor<Boolean> executor;
+  private FailsafeExecutor<Result> executor;
   private final ForwarderMetricsRegistry metricsRegistry;
 
   @Inject
@@ -68,7 +68,7 @@
       Configuration cfg,
       Provider<Set<PeerInfo>> peerInfoProvider,
       @EventGson Gson gson,
-      @RestForwarderExecutor FailsafeExecutor<Boolean> executor,
+      @RestForwarderExecutor FailsafeExecutor<Result> executor,
       ForwarderMetricsRegistry metricsRegistry) {
     this.httpSession = httpClient;
     this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName);
@@ -77,10 +77,14 @@
     this.gson = gson;
     this.executor = executor;
     this.metricsRegistry = metricsRegistry;
+    this.executor.onComplete(
+        ev -> {
+          this.metricsRegistry.get(ev.getResult().getType()).recordRetries(ev.getAttemptCount());
+        });
   }
 
   @Override
-  public CompletableFuture<Boolean> indexAccount(final int accountId, IndexEvent event) {
+  public CompletableFuture<Result> indexAccount(final int accountId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         EventType.INDEX_ACCOUNT_UPDATE,
@@ -92,8 +96,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> indexChange(
-      String projectName, int changeId, IndexEvent event) {
+  public CompletableFuture<Result> indexChange(String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         EventType.INDEX_CHANGE_UPDATE,
@@ -105,7 +108,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> batchIndexChange(
+  public CompletableFuture<Result> batchIndexChange(
       String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
@@ -118,7 +121,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> deleteChangeFromIndex(final int changeId, IndexEvent event) {
+  public CompletableFuture<Result> deleteChangeFromIndex(final int changeId, IndexEvent event) {
     return execute(
         RequestMethod.DELETE,
         EventType.INDEX_CHANGE_DELETION,
@@ -130,7 +133,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> indexGroup(final String uuid, IndexEvent event) {
+  public CompletableFuture<Result> indexGroup(final String uuid, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         EventType.INDEX_GROUP_UPDATE,
@@ -157,7 +160,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent event) {
+  public CompletableFuture<Result> indexProject(String projectName, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         EventType.INDEX_PROJECT_UPDATE,
@@ -169,7 +172,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> send(final Event event) {
+  public CompletableFuture<Result> send(final Event event) {
     return execute(
         RequestMethod.POST,
         EventType.EVENT_SENT,
@@ -181,7 +184,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> evict(final String cacheName, final Object key) {
+  public CompletableFuture<Result> evict(final String cacheName, final Object key) {
     String json = gson.toJson(key);
     return execute(
         RequestMethod.POST,
@@ -194,7 +197,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> addToProjectList(String projectName) {
+  public CompletableFuture<Result> addToProjectList(String projectName) {
     return execute(
         RequestMethod.POST,
         EventType.PROJECT_LIST_ADDITION,
@@ -205,7 +208,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> removeFromProjectList(String projectName) {
+  public CompletableFuture<Result> removeFromProjectList(String projectName) {
     return execute(
         RequestMethod.DELETE,
         EventType.PROJECT_LIST_DELETION,
@@ -216,7 +219,7 @@
   }
 
   @Override
-  public CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName) {
+  public CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName) {
     return execute(
         RequestMethod.DELETE,
         EventType.INDEX_CHANGE_DELETION_ALL_OF_PROJECT,
@@ -230,7 +233,7 @@
     return Joiner.on("/").join("cache", Constants.PROJECT_LIST);
   }
 
-  private CompletableFuture<Boolean> execute(
+  private CompletableFuture<Result> execute(
       RequestMethod method,
       EventType eventType,
       String action,
@@ -240,7 +243,7 @@
     return execute(method, eventType, action, endpoint, id, null, requestStart);
   }
 
-  private CompletableFuture<Boolean> execute(
+  private CompletableFuture<Result> execute(
       RequestMethod method,
       EventType eventType,
       String action,
@@ -250,14 +253,19 @@
       Instant requestStart) {
     log.atFine().log("Scheduling forwarding of: %s %s %s", action, id, payload);
     return peerInfoProvider.get().stream()
-        .map(peer -> createRequest(method, peer, action, endpoint, id, payload, requestStart))
+        .map(
+            peer ->
+                createRequest(method, eventType, peer, action, endpoint, id, payload, requestStart))
         .map(r -> executor.getAsync(() -> r.execute()))
         .reduce(
-            CompletableFuture.completedFuture(true),
-            (a, b) -> a.thenCombine(b, (left, right) -> left && right))
+            CompletableFuture.completedFuture(new Result(eventType, true)),
+            (a, b) ->
+                a.thenCombine(
+                    b,
+                    (left, right) -> new Result(eventType, left.getResult() && right.getResult())))
         .thenApplyAsync(
             result -> {
-              metricsRegistry.get(eventType).recordResult(result);
+              metricsRegistry.get(eventType).recordResult(result.getResult());
               metricsRegistry
                   .get(eventType)
                   .recordLatency(Duration.between(requestStart, Instant.now()).toMillis());
@@ -267,6 +275,7 @@
 
   private Request createRequest(
       RequestMethod method,
+      EventType eventType,
       PeerInfo peer,
       String action,
       String endpoint,
@@ -274,7 +283,7 @@
       Object payload,
       Instant createdOn) {
     String destination = peer.getDirectUrl();
-    return new Request(action, id, destination) {
+    return new Request(eventType, action, id, destination) {
       @Override
       HttpResult send() throws IOException {
         String request = Joiner.on("/").join(destination, pluginRelativePath, endpoint, id);
@@ -290,13 +299,15 @@
   }
 
   protected abstract class Request {
+    private final EventType eventType;
     private final String action;
     private final Object key;
     private final String destination;
 
     private int execCnt;
 
-    Request(String action, Object key, String destination) {
+    Request(EventType eventType, String action, Object key, String destination) {
+      this.eventType = eventType;
       this.action = action;
       this.key = key;
       this.destination = destination;
@@ -307,13 +318,13 @@
       return String.format("%s:%s => %s (try #%d)", action, key, destination, execCnt);
     }
 
-    boolean execute() throws ForwardingException {
+    Result execute() {
       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;
+        return new Result(eventType, true);
       } catch (ForwardingException e) {
         int maxTries = cfg.http().maxTries();
         log.atFine().withCause(e).log(
@@ -322,10 +333,10 @@
           log.atSevere().withCause(e).log(
               "%s %s towards %s failed with unrecoverable error; giving up",
               action, key, destination);
-          throw e;
+          return new Result(eventType, false, false);
         }
       }
-      return false;
+      return new Result(eventType, false);
     }
 
     void tryOnce() throws ForwardingException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
index 39c9cc0..5d38b16 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
 import com.google.inject.TypeLiteral;
@@ -29,7 +30,7 @@
     bind(HttpSession.class);
     bind(Forwarder.class).to(RestForwarder.class);
 
-    bind(new TypeLiteral<FailsafeExecutor<Boolean>>() {})
+    bind(new TypeLiteral<FailsafeExecutor<Result>>() {})
         .annotatedWith(RestForwarderExecutor.class)
         .toProvider(FailsafeExecutorProvider.class)
         .in(Scopes.SINGLETON);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
index 8dc9f43..39d388c 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetrics;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
@@ -94,8 +95,8 @@
     RspList<Object> OK = new RspList<>(Map.of(A1, RSP_OK, A2, RSP_OK));
     when(dispatcher.castMessage(any(), any(), any())).thenReturn(OK);
 
-    CompletableFuture<Boolean> result = forwarder.indexAccount(100, new IndexEvent());
-    assertThat(result.get()).isTrue();
+    CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().getResult()).isTrue();
     verify(dispatcher, times(1)).castMessage(any(), any(), any());
   }
 
@@ -106,8 +107,8 @@
     RspList<Object> FAIL = new RspList<>(Map.of(A1, RSP_OK, A2, RSP_FAIL));
     when(dispatcher.castMessage(any(), any(), any())).thenReturn(FAIL, OK);
 
-    CompletableFuture<Boolean> result = forwarder.indexAccount(100, new IndexEvent());
-    assertThat(result.get()).isTrue();
+    CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().getResult()).isTrue();
     verify(dispatcher, times(2)).castMessage(any(), any(), any());
   }
 
@@ -118,8 +119,8 @@
     // return FAIL x MAX_TRIES
     when(dispatcher.castMessage(any(), any(), any())).thenReturn(FAIL, FAIL, FAIL);
 
-    CompletableFuture<Boolean> result = forwarder.indexAccount(100, new IndexEvent());
-    assertThat(result.get()).isFalse();
+    CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent());
+    assertThat(result.get().getResult()).isFalse();
     verify(dispatcher, times(MAX_TRIES)).castMessage(any(), any(), any());
   }
 }
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 1df4cec..94ff2fb 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
@@ -145,7 +145,8 @@
     assertThat(
             forwarder
                 .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -156,7 +157,8 @@
     assertThat(
             forwarder
                 .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -167,7 +169,8 @@
     assertThat(
             forwarder
                 .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -175,7 +178,11 @@
   public void testIndexGroupOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .indexGroup(UUID, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -183,14 +190,22 @@
   public void testIndexGroupFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .indexGroup(UUID, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
   @Test
   public void testIndexGroupThrowsException() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any())).thenThrow(IOException.class);
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .indexGroup(UUID, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -201,7 +216,8 @@
     assertThat(
             forwarder
                 .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -212,7 +228,8 @@
     assertThat(
             forwarder
                 .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -223,7 +240,8 @@
     assertThat(
             forwarder
                 .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -231,7 +249,11 @@
   public void testIndexBatchChangeOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+    assertThat(
+            forwarder
+                .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get()
+                .getResult())
         .isTrue();
   }
 
@@ -239,7 +261,11 @@
   public void testIndexBatchChangeFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+    assertThat(
+            forwarder
+                .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get()
+                .getResult())
         .isFalse();
   }
 
@@ -247,7 +273,11 @@
   public void testIndexBatchChangeThrowsException() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any()))
         .thenThrow(IOException.class);
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+    assertThat(
+            forwarder
+                .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get()
+                .getResult())
         .isFalse();
   }
 
@@ -258,7 +288,8 @@
     assertThat(
             forwarder
                 .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -269,7 +300,8 @@
     assertThat(
             forwarder
                 .deleteAllChangesForProject(Project.nameKey(PROJECT_NAME))
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -280,7 +312,8 @@
     assertThat(
             forwarder
                 .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -290,7 +323,8 @@
     assertThat(
             forwarder
                 .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -298,20 +332,20 @@
   public void testEventSentOK() throws Exception {
     when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isTrue();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).getResult()).isTrue();
   }
 
   @Test
   public void testEventSentFailed() throws Exception {
     when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).getResult()).isFalse();
   }
 
   @Test
   public void testEventSentThrowsException() throws Exception {
     when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any())).thenThrow(IOException.class);
-    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).getResult()).isFalse();
   }
 
   @Test
@@ -320,7 +354,11 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECTS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -330,7 +368,11 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.ACCOUNTS)), eq(keyJson), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.ACCOUNTS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -341,7 +383,11 @@
     String endpoint = buildCacheEndpoint(Constants.GROUPS);
     when(httpSessionMock.post(eq(endpoint), eq(keyJson), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.GROUPS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -353,7 +399,10 @@
             eq(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE)), eq(keyJson), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
     assertThat(
-            forwarder.evict(Constants.GROUPS_BYINCLUDE, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+            forwarder
+                .evict(Constants.GROUPS_BYINCLUDE, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -363,7 +412,11 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.GROUPS_MEMBERS)), eq(keyJson), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.GROUPS_MEMBERS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -373,7 +426,11 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECTS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -383,7 +440,11 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any()))
         .thenThrow(IOException.class);
-    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECTS, key)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -396,7 +457,11 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .addToProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -405,7 +470,11 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .addToProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -414,7 +483,11 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any()))
         .thenThrow(IOException.class);
-    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .addToProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -423,7 +496,11 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .removeFromProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -432,7 +509,11 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .removeFromProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -441,7 +522,11 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any()))
         .thenThrow(IOException.class);
-    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+    assertThat(
+            forwarder
+                .removeFromProjectList(projectName)
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -459,7 +544,8 @@
     assertThat(
             forwarder
                 .evict(Constants.PROJECT_LIST, new Object())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -473,7 +559,8 @@
     assertThat(
             forwarder
                 .evict(Constants.PROJECT_LIST, new Object())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isTrue();
   }
 
@@ -486,7 +573,8 @@
     assertThat(
             forwarder
                 .evict(Constants.PROJECT_LIST, new Object())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .isFalse();
   }
 
@@ -500,7 +588,8 @@
     assertThat(
             forwarder
                 .evict(Constants.PROJECT_LIST, new Object())
-                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)
+                .getResult())
         .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 a8aa4a9..a986b90 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
@@ -24,7 +24,9 @@
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
@@ -75,12 +77,17 @@
     when(changeCheckerMock.newIndexEvent()).thenReturn(Optional.of(new IndexEvent()));
 
     when(forwarder.indexAccount(eq(ACCOUNT_ID), any()))
-        .thenReturn(CompletableFuture.completedFuture(true));
+        .thenReturn(
+            CompletableFuture.completedFuture(new Result(EventType.INDEX_ACCOUNT_UPDATE, true)));
     when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any()))
-        .thenReturn(CompletableFuture.completedFuture(true));
-    when(forwarder.indexGroup(eq(UUID), any())).thenReturn(CompletableFuture.completedFuture(true));
+        .thenReturn(
+            CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_DELETION, true)));
+    when(forwarder.indexGroup(eq(UUID), any()))
+        .thenReturn(
+            CompletableFuture.completedFuture(new Result(EventType.INDEX_GROUP_UPDATE, true)));
     when(forwarder.indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()))
-        .thenReturn(CompletableFuture.completedFuture(true));
+        .thenReturn(
+            CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_UPDATE, true)));
 
     setUpIndexEventHandler(currCtx);
   }