Repository: jenkinsci/prometheus-plugin (java)

Issue:
Possible deadlock on startup
### Jenkins and plugins versions report

The initialization of the Prometheus plugin may cause a deadlock on startup. In a scenario where a plugin / process causes the initialization of the `DefaultPrometheusMetrics` in the boot up `org.jvnet.hudson.reactor.Reactor.runTask`, if there are pipelines running (for example resumed pipelines) then there is a chance to hit it:

Deadlock details:

```
Found one Java-level deadlock:
=============================
"Periodic background build discarder thread":
  waiting to lock monitor 0x00007fd8800058a8 (object 0x000000064782f100, a hudson.ExtensionList$Lock),
  which is held by "GroovyInitScript.init"
"GroovyInitScript.init":
  waiting to lock monitor 0x00007fd86a766828 (object 0x0000000631c002a0, a org.jenkinsci.plugins.workflow.cps.CpsFlowExecution),
  which is held by "Running CpsFlowExecution[Owner[mypipeline #123]]"
"Running CpsFlowExecution[Owner[mypipeline #123]]":
  waiting to lock monitor 0x00007fd8800058a8 (object 0x000000064782f100, a hudson.ExtensionList$Lock),
  which is held by "GroovyInitScript.init"

Java stack information for the threads listed above:
===================================================
"Periodic background build discarder thread":
	at hudson.ExtensionList.ensureLoaded(ExtensionList.java:315)
	- waiting to lock <0x000000064782f100> (a hudson.ExtensionList$Lock)
	at hudson.ExtensionList.size(ExtensionList.java:191)
	at hudson.ExtensionList.lookupSingleton(ExtensionList.java:450)
	at jenkins.model.GlobalBuildDiscarderConfiguration.get(GlobalBuildDiscarderConfiguration.java:48)
	at jenkins.model.BackgroundGlobalBuildDiscarder.processJob(BackgroundGlobalBuildDiscarder.java:61)
	at jenkins.model.BackgroundGlobalBuildDiscarder.execute(BackgroundGlobalBuildDiscarder.java:55)
	at hudson.model.AsyncPeriodicWork.lambda$doRun$0(AsyncPeriodicWork.java:100)
	at hudson.model.AsyncPeriodicWork$$Lambda$360/234513123.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"GroovyInitScript.init":
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.getCurrentHeads(CpsFlowExecution.java:981)
	- waiting to lock <0x0000000631c002a0> (a org.jenkinsci.plugins.workflow.cps.CpsFlowExecution)
	at org.jenkinsci.plugins.workflow.flow.FlowExecution.isComplete(FlowExecution.java:208)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.isComplete(CpsFlowExecution.java:1238)
	at org.jenkinsci.plugins.workflow.cps.RunningFlowActions.createFor(RunningFlowActions.java:52)
	at org.jenkinsci.plugins.workflow.cps.RunningFlowActions.createFor(RunningFlowActions.java:42)
	at hudson.model.Actionable.createFor(Actionable.java:114)
	at hudson.model.Actionable.getAction(Actionable.java:335)
	at org.jenkinsci.plugins.prometheus.JobCollector.hasTestResults(JobCollector.java:395)
	at org.jenkinsci.plugins.prometheus.JobCollector.appendJobMetrics(JobCollector.java:296)
	at org.jenkinsci.plugins.prometheus.JobCollector.lambda$collect$0(JobCollector.java:220)
	at org.jenkinsci.plugins.prometheus.JobCollector$$Lambda$270/321878244.accept(Unknown Source)
	at org.jenkinsci.plugins.prometheus.util.Jobs.forEachJob(Jobs.java:14)
	at org.jenkinsci.plugins.prometheus.JobCollector.collect(JobCollector.java:214)
	at io.prometheus.client.CollectorRegistry.collectorNames(CollectorRegistry.java:100)
	at io.prometheus.client.CollectorRegistry.register(CollectorRegistry.java:50)
	at org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics.<init>(DefaultPrometheusMetrics.java:29)
	at org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics$$FastClassByGuice$$af5295cb.newInstance(<generated>)
	at com.google.inject.internal.cglib.reflect.$FastConstructor.newInstance(FastConstructor.java:40)
	at com.google.inject.internal.DefaultConstructionProxyFactory$1.newInstance(DefaultConstructionProxyFactory.java:61)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:105)
	at com.google.inject.internal.ConstructorInjector.access$000(ConstructorInjector.java:32)
	at com.google.inject.internal.ConstructorInjector$1.call(ConstructorInjector.java:89)
	at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:115)
	at hudson.ExtensionFinder$GuiceFinder$SezpozModule.onProvision(ExtensionFinder.java:566)
	at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:126)
	at com.google.inject.internal.ProvisionListenerStackCallback.provision(ProvisionListenerStackCallback.java:68)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:87)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:267)
	at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
	at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:145)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
	at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
	at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
	at com.google.inject.internal.SingleMethodInjector.inject(SingleMethodInjector.java:84)
	at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:132)
	at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:114)
	at com.google.inject.internal.ConstructorInjector.access$000(ConstructorInjector.java:32)
	at com.google.inject.internal.ConstructorInjector$1.call(ConstructorInjector.java:89)
	at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:115)
	at hudson.ExtensionFinder$GuiceFinder$SezpozModule.onProvision(ExtensionFinder.java:566)
	at com.google.inject.internal.ProvisionListenerStackCallback$Provision.provision(ProvisionListenerStackCallback.java:126)
	at com.google.inject.internal.ProvisionListenerStackCallback.provision(ProvisionListenerStackCallback.java:68)
	at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:87)
	at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:267)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
	at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
	at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:145)
	at hudson.ExtensionFinder$GuiceFinder$FaultTolerantScope$1.get(ExtensionFinder.java:440)
	at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
	at com.google.inject.internal.InjectorImpl$2$1.call(InjectorImpl.java:1016)
	at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
	at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1012)
	at hudson.ExtensionFinder$GuiceFinder._find(ExtensionFinder.java:402)
	at hudson.ExtensionFinder$GuiceFinder.find(ExtensionFinder.java:393)
	at hudson.ClassicPluginStrategy.findComponents(ClassicPluginStrategy.java:349)
	at hudson.ExtensionList.load(ExtensionList.java:381)
	at hudson.ExtensionList.ensureLoaded(ExtensionList.java:317)
	- locked <0x000000064782f100> (a hudson.ExtensionList$Lock)
	at hudson.ExtensionList.iterator(ExtensionList.java:172)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2025)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2066)
	at org.codehaus.groovy.runtime.dgm$163.invoke(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at cli-shutdown$_run_closure1.doCall(cli-shutdown.groovy:4)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
	at cli-shutdown.run(cli-shutdown.groovy:8)
	at groovy.lang.GroovyShell.evaluate(GroovyShell.java:585)
	at jenkins.util.groovy.GroovyHookScript.execute(GroovyHookScript.java:138)
	at jenkins.util.groovy.GroovyHookScript.execute(GroovyHookScript.java:129)
	at jenkins.util.groovy.GroovyHookScript.run(GroovyHookScript.java:112)
	at hudson.init.impl.GroovyInitScript.init(GroovyInitScript.java:42)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at hudson.init.TaskMethodFinder.invoke(TaskMethodFinder.java:104)
	at hudson.init.TaskMethodFinder$TaskImpl.run(TaskMethodFinder.java:175)
	at org.jvnet.hudson.reactor.Reactor.runTask(Reactor.java:296)
	at jenkins.model.Jenkins$5.runTask(Jenkins.java:1131)
	at org.jvnet.hudson.reactor.Reactor$2.run(Reactor.java:214)
	at org.jvnet.hudson.reactor.Reactor$Node.run(Reactor.java:117)
	at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
"Running CpsFlowExecution[Owner[mypipeline #123]]":
	at hudson.ExtensionList.ensureLoaded(ExtensionList.java:315)
	- waiting to lock <0x000000064782f100> (a hudson.ExtensionList$Lock)
	at hudson.ExtensionList.size(ExtensionList.java:191)
	at java.util.AbstractCollection.toArray(AbstractCollection.java:136)
	at java.util.ArrayList.addAll(ArrayList.java:583)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.getListenersToRun(CpsFlowExecution.java:1449)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.notifyListeners(CpsFlowExecution.java:1455)
	at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.notifyNewHead(CpsThreadGroup.java:472)
	at org.jenkinsci.plugins.workflow.cps.FlowHead.setNewHead(FlowHead.java:157)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.onProgramEnd(CpsFlowExecution.java:1255)
	- locked <0x0000000631c002a0> (a org.jenkinsci.plugins.workflow.cps.CpsFlowExecution)
	at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.croak(CpsFlowExecution.java:882)
	at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.reportProblem(CpsVmExecutorService.java:57)
	at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.access$100(CpsVmExecutorService.java:23)
	at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:69)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
	at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
	at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
```

### What Operating System are you using (both controller, and any agents involved in the problem)?

Unix

### Reproduction steps

* Set up a groovy post init script that access ExtensionsList, like for example https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-controllers/disable-jenkins-cli
* create a simple pipeline that just echo something
* launch many many builds and stop Jenkins
* Start jenkins

It will not always reproduce the problem, since it is a race condition, but there is a chance that it does.

### Expected Results

Jenkins starts, always.

### Actual Results

Jenkins sometimes nevers finish starting up because of a deadlock.

### Anything else?

The [DefaultPrometheusMetrics](https://github.com/jenkinsci/prometheus-plugin/blob/master/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java) should maybe be initialized lazily.

### Are you interested in contributing a fix?

Sure.

Pull request:
Changing creation of service classes away from explicit guice usage
usage to avoid duplicate instantiation of DefaultPrometheusMetrics class.

Fixes #635 




Patch:
diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java b/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java
deleted file mode 100644
index 5d51634..0000000
--- a/src/main/java/org/jenkinsci/plugins/prometheus/context/Context.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.jenkinsci.plugins.prometheus.context;
-
-import com.google.inject.AbstractModule;
-import hudson.Extension;
-import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics;
-import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics;
-
-@Extension
-public class Context extends AbstractModule {
-
-    @Override
-    public void configure() {
-        bind(PrometheusMetrics.class).to(DefaultPrometheusMetrics.class).in(com.google.inject.Singleton.class);
-    }
-
-}
diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java b/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java
index 5e8f38d..35d1645 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java
@@ -1,7 +1,7 @@
 package org.jenkinsci.plugins.prometheus.rest;
 
-import com.google.inject.Inject;
 import hudson.Extension;
+import hudson.ExtensionList;
 import hudson.model.UnprotectedRootAction;
 import hudson.util.HttpResponses;
 import io.prometheus.client.exporter.common.TextFormat;
@@ -15,14 +15,7 @@ import org.kohsuke.stapler.StaplerResponse;
 
 @Extension
 public class PrometheusAction implements UnprotectedRootAction {
-
-    private PrometheusMetrics prometheusMetrics;
-
-    @Inject
-    public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) {
-        this.prometheusMetrics = prometheusMetrics;
-    }
-
+    
     @Override
     public String getIconFileName() {
         return null;
@@ -56,6 +49,7 @@ public class PrometheusAction implements UnprotectedRootAction {
     }
 
     private HttpResponse prometheusResponse() {
+        PrometheusMetrics prometheusMetrics = ExtensionList.lookupSingleton(PrometheusMetrics.class);
         return (request, response, node) -> {
             response.setStatus(StaplerResponse.SC_OK);
             response.setContentType(TextFormat.CONTENT_TYPE_004);
diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java b/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java
index 71210e1..e1f53fb 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java
@@ -1,5 +1,6 @@
 package org.jenkinsci.plugins.prometheus.service;
 
+import hudson.Extension;
 import hudson.ExtensionList;
 import hudson.init.InitMilestone;
 import hudson.init.Initializer;
@@ -8,15 +9,8 @@ import io.prometheus.client.CollectorRegistry;
 import io.prometheus.client.dropwizard.DropwizardExports;
 import io.prometheus.client.exporter.common.TextFormat;
 import io.prometheus.client.hotspot.DefaultExports;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.concurrent.atomic.AtomicReference;
 import jenkins.metrics.api.Metrics;
-import org.jenkinsci.plugins.prometheus.CodeCoverageCollector;
-import org.jenkinsci.plugins.prometheus.DiskUsageCollector;
-import org.jenkinsci.plugins.prometheus.ExecutorCollector;
-import org.jenkinsci.plugins.prometheus.JenkinsStatusCollector;
-import org.jenkinsci.plugins.prometheus.JobCollector;
+import org.jenkinsci.plugins.prometheus.*;
 import org.jenkinsci.plugins.prometheus.config.disabledmetrics.FilteredMetricEnumeration;
 import org.jenkinsci.plugins.prometheus.util.JenkinsNodeBuildsSampleBuilder;
 import org.kohsuke.accmod.Restricted;
@@ -24,10 +18,15 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicReference;
+
 @Restricted(NoExternalUse.class)
+@Extension
 public class DefaultPrometheusMetrics implements PrometheusMetrics {
 
-    private static final Logger logger = LoggerFactory.getLogger(DefaultPrometheusMetrics.class);
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPrometheusMetrics.class);
 
     private volatile boolean initialized = false;
     private volatile boolean initializing = false;
@@ -40,10 +39,10 @@ public class DefaultPrometheusMetrics implements PrometheusMetrics {
     }
 
     @Initializer(after = InitMilestone.JOB_LOADED)
-    public void init() {
+    public void registerCollectors() {
         if (!initialized && !initializing) {
             initializing = true;
-            logger.debug("Initializing...");
+            LOGGER.debug("Initializing...");
             collectorRegistry.register(new JobCollector());
             collectorRegistry.register(new JenkinsStatusCollector());
             collectorRegistry.register(
@@ -67,14 +66,14 @@ public class DefaultPrometheusMetrics implements PrometheusMetrics {
     @Override
     public void collectMetrics() {
         if(!initialized) {
-            logger.debug("Not initialized");
+            LOGGER.debug("Not initialized");
             return;
         }
         try (StringWriter buffer = new StringWriter()) {
             TextFormat.write004(buffer, new FilteredMetricEnumeration(collectorRegistry.metricFamilySamples().asIterator()));
             cachedMetrics.set(buffer.toString());
         } catch (IOException e) {
-            logger.debug("Unable to collect metrics");
+            LOGGER.debug("Unable to collect metrics");
         }
     }
 }
diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java
index 2955171..b0aa93d 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java
@@ -1,7 +1,7 @@
 package org.jenkinsci.plugins.prometheus.service;
 
-import com.google.inject.Inject;
 import hudson.Extension;
+import hudson.ExtensionList;
 import hudson.model.AsyncPeriodicWork;
 import hudson.model.TaskListener;
 import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration;
@@ -16,16 +16,11 @@ public class PrometheusAsyncWorker extends AsyncPeriodicWork {
 
     private static final Logger logger = LoggerFactory.getLogger(PrometheusAsyncWorker.class);
 
-    private PrometheusMetrics prometheusMetrics;
 
     public PrometheusAsyncWorker() {
         super("prometheus_async_worker");
     }
 
-    @Inject
-    public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) {
-        this.prometheusMetrics = prometheusMetrics;
-    }
 
     @Override
     public long getRecurrencePeriod() {
@@ -38,6 +33,7 @@ public class PrometheusAsyncWorker extends AsyncPeriodicWork {
     @Override
     public void execute(TaskListener taskListener) {
         logger.debug("Collecting prometheus metrics");
+        PrometheusMetrics prometheusMetrics = ExtensionList.lookupSingleton(PrometheusMetrics.class);
         prometheusMetrics.collectMetrics();
         logger.debug("Prometheus metrics collected successfully");
     }
diff --git a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusMetrics.java b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusMetrics.java
index fd5103e..9dadc68 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusMetrics.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusMetrics.java
@@ -1,6 +1,8 @@
 package org.jenkinsci.plugins.prometheus.service;
 
-public interface PrometheusMetrics {
+import hudson.ExtensionPoint;
+
+public interface PrometheusMetrics extends ExtensionPoint {
 
     String getMetrics();
 


Test patch:
diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/context/ContextTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/context/ContextTest.java
deleted file mode 100644
index ea8d935..0000000
--- a/src/test/java/org/jenkinsci/plugins/prometheus/context/ContextTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.jenkinsci.plugins.prometheus.context;
-
-import com.codahale.metrics.MetricRegistry;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import jenkins.metrics.api.Metrics;
-import jenkins.model.Jenkins;
-import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration;
-import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics;
-import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-
-import static org.mockito.Mockito.*;
-
-public class ContextTest {
-
-    @Test
-    public void testCanInjectContext() {
-        try (MockedStatic<Jenkins> jenkinsStatic = mockStatic(Jenkins.class);
-             MockedStatic<PrometheusConfiguration> prometheusConfigurationStatic = mockStatic(PrometheusConfiguration.class);
-             MockedStatic<Metrics> metricsStatic = mockStatic(Metrics.class)) {
-            Jenkins mockedJenkins = mock(Jenkins.class);
-            jenkinsStatic.when(Jenkins::get).thenReturn(mockedJenkins);
-
-            MetricRegistry mockedMetrics = mock(MetricRegistry.class);
-            metricsStatic.when(Metrics::metricRegistry).thenReturn(mockedMetrics);
-
-            PrometheusConfiguration mockedPrometheusConfiguration = mock(PrometheusConfiguration.class);
-            when(mockedPrometheusConfiguration.getLabeledBuildParameterNamesAsArray()).thenReturn(new String[]{});
-            when(mockedPrometheusConfiguration.getDefaultNamespace()).thenReturn("default");
-            prometheusConfigurationStatic.when(PrometheusConfiguration::get).thenReturn(mockedPrometheusConfiguration);
-
-            Injector injector = Guice.createInjector(new Context());
-            PrometheusMetrics prometheusMetrics = injector.getInstance(PrometheusMetrics.class);
-
-            Assertions.assertNotNull(prometheusMetrics);
-            Assertions.assertEquals(DefaultPrometheusMetrics.class, prometheusMetrics.getClass());
-
-            PrometheusMetrics prometheusMetrics2 = injector.getInstance(PrometheusMetrics.class);
-
-            Assertions.assertEquals(prometheusMetrics, prometheusMetrics2, "Should be the same as it's a singleton!");
-
-
-        }
-
-    }
-
-}
\ No newline at end of file
diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java
index 58bc253..b750b67 100644
--- a/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java
+++ b/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java
@@ -1,5 +1,6 @@
 package org.jenkinsci.plugins.prometheus.rest;
 
+import hudson.ExtensionList;
 import io.prometheus.client.exporter.common.TextFormat;
 import jenkins.metrics.api.Metrics;
 import jenkins.model.Jenkins;
@@ -91,21 +92,24 @@ public class PrometheusActionTest {
         PrometheusMetrics prometheusMetrics = mock(PrometheusMetrics.class);
         String responseBody = "testMetric";
         when(prometheusMetrics.getMetrics()).thenReturn(responseBody);
-        action.setPrometheusMetrics(prometheusMetrics);
-        StaplerRequest request = mock(StaplerRequest.class);
-        String url = "prometheus";
-        when(request.getRestOfPath()).thenReturn(url);
-
-        // when
-        HttpResponse actual = action.doDynamic(request);
+        try (MockedStatic<ExtensionList> extensionListMockedStatic = mockStatic(ExtensionList.class)) {
+            extensionListMockedStatic.when(() -> ExtensionList.lookupSingleton(PrometheusMetrics.class)).thenReturn(prometheusMetrics);
+            StaplerRequest request = mock(StaplerRequest.class);
+            String url = "prometheus";
+            when(request.getRestOfPath()).thenReturn(url);
+
+            // when
+            HttpResponse actual = action.doDynamic(request);
+
+            // then
+            AssertStaplerResponse.from(actual)
+                    .call()
+                    .assertHttpStatus(HTTP_OK)
+                    .assertContentType(TextFormat.CONTENT_TYPE_004)
+                    .assertHttpHeader("Cache-Control", "must-revalidate,no-cache,no-store")
+                    .assertBody(responseBody);
+        }
 
-        // then
-        AssertStaplerResponse.from(actual)
-            .call()
-            .assertHttpStatus(HTTP_OK)
-            .assertContentType(TextFormat.CONTENT_TYPE_004)
-            .assertHttpHeader("Cache-Control", "must-revalidate,no-cache,no-store")
-            .assertBody(responseBody);
     }
 
     private static class AssertStaplerResponse {
diff --git a/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java b/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java
index 8ba3677..2d6e911 100644
--- a/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java
+++ b/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java
@@ -1,5 +1,6 @@
 package org.jenkinsci.plugins.prometheus.service;
 
+import hudson.ExtensionList;
 import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration;
 import org.junit.jupiter.api.Test;
 import org.jvnet.hudson.test.Issue;
@@ -20,15 +21,15 @@ public class PrometheusAsyncWorkerTest {
         // given
         PrometheusAsyncWorker asyncWorker = new PrometheusAsyncWorker();
         PrometheusMetrics metrics = new TestPrometheusMetrics();
-        asyncWorker.setPrometheusMetrics(metrics);
-
-        // when
-        asyncWorker.execute(null);
-
-        // then
-        String actual = metrics.getMetrics();
-        assertEquals("1", actual);
+        try (MockedStatic<ExtensionList> extensionListMockedStatic = mockStatic(ExtensionList.class)) {
+            extensionListMockedStatic.when(() -> ExtensionList.lookupSingleton(PrometheusMetrics.class)).thenReturn(metrics);
+            // when
+            asyncWorker.execute(null);
 
+            // then
+            String actual = metrics.getMetrics();
+            assertEquals("1", actual);
+        }
     }
     @Test
     public void testConvertSecondsToMillis() {