Deregister queue metrics upon shutdown

Historically, the registration of metrics in Gerrit has been a
fire-and-forget operation, without the possibility of having access to
the registration handle and remove them as needed.

The initial design of metrics made it almost impossible for plugins to
use them effectively, until I06d4853 introduced the possibility of
having access to the registration handles and control them as part of
the plugins lifecycle.

The I06d4853 missed however, the integration with the WorkQueue, which
was left in its initial design issue, without any ability to have queues
with metrics in plugins.

Add the missing deregistration path as a follow up of I06d4853 to the
WorkQueue so that plugins can easily create queues with associated
metrics, without the issue of ending up with reloading/restarting
failures because of duplicated metric names.

Release-Notes: Deregister queue metrics upon executor shutdown
Bug: Issue 461836624
Change-Id: I9ed45cedd00d39beb3f514d23ed2e08c853aae72
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index 3032bfe..54c9eb6 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -20,6 +20,7 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.MetricMaker;
@@ -243,6 +244,7 @@
   private class Executor extends ScheduledThreadPoolExecutor {
     private final ConcurrentHashMap<Integer, Task<?>> all;
     private final String queueName;
+    private final List<RegistrationHandle> metricsRegistrationHandles;
 
     Executor(int corePoolSize, final String queueName) {
       super(
@@ -267,6 +269,7 @@
               corePoolSize + 4 // concurrency level
               );
       this.queueName = queueName;
+      this.metricsRegistrationHandles = new ArrayList<>();
     }
 
     @Override
@@ -345,44 +348,69 @@
     }
 
     private void buildMetrics(String queueName) {
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "max_pool_size"),
-          Long.class,
-          new Description("Maximum allowed number of threads in the pool")
-              .setGauge()
-              .setUnit("threads"),
-          () -> (long) getMaximumPoolSize());
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "pool_size"),
-          Long.class,
-          new Description("Current number of threads in the pool").setGauge().setUnit("threads"),
-          () -> (long) getPoolSize());
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "active_threads"),
-          Long.class,
-          new Description("Number number of threads that are actively executing tasks")
-              .setGauge()
-              .setUnit("threads"),
-          () -> (long) getActiveCount());
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "scheduled_tasks"),
-          Integer.class,
-          new Description("Number of scheduled tasks in the queue").setGauge().setUnit("tasks"),
-          () -> getQueue().size());
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "total_scheduled_tasks_count"),
-          Long.class,
-          new Description("Total number of tasks that have been scheduled for execution")
-              .setCumulative()
-              .setUnit("tasks"),
-          this::getTaskCount);
-      metrics.newCallbackMetric(
-          getMetricName(queueName, "total_completed_tasks_count"),
-          Long.class,
-          new Description("Total number of tasks that have completed execution")
-              .setCumulative()
-              .setUnit("tasks"),
-          this::getCompletedTaskCount);
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "max_pool_size"),
+              Long.class,
+              new Description("Maximum allowed number of threads in the pool")
+                  .setGauge()
+                  .setUnit("threads"),
+              () -> (long) getMaximumPoolSize()));
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "pool_size"),
+              Long.class,
+              new Description("Current number of threads in the pool")
+                  .setGauge()
+                  .setUnit("threads"),
+              () -> (long) getPoolSize()));
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "active_threads"),
+              Long.class,
+              new Description("Number number of threads that are actively executing tasks")
+                  .setGauge()
+                  .setUnit("threads"),
+              () -> (long) getActiveCount()));
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "scheduled_tasks"),
+              Integer.class,
+              new Description("Number of scheduled tasks in the queue").setGauge().setUnit("tasks"),
+              () -> getQueue().size()));
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "total_scheduled_tasks_count"),
+              Long.class,
+              new Description("Total number of tasks that have been scheduled for execution")
+                  .setCumulative()
+                  .setUnit("tasks"),
+              this::getTaskCount));
+      metricsRegistrationHandles.add(
+          metrics.newCallbackMetric(
+              getMetricName(queueName, "total_completed_tasks_count"),
+              Long.class,
+              new Description("Total number of tasks that have completed execution")
+                  .setCumulative()
+                  .setUnit("tasks"),
+              this::getCompletedTaskCount));
+    }
+
+    @Override
+    public void shutdown() {
+      super.shutdown();
+      deregisterMetrics();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      List<Runnable> runnables = super.shutdownNow();
+      deregisterMetrics();
+      return runnables;
+    }
+
+    private void deregisterMetrics() {
+      metricsRegistrationHandles.forEach(RegistrationHandle::remove);
     }
 
     private String getMetricName(String queueName, String metricName) {