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:
Remove Guice + use Singleton to fix initilization potential deadlock
Fixes #635 . Second attempt...
Removed Guice. 
Using a Singleton with a simple constructor. 
Collectors get register with the Jenkins initialization milestones. 
Job related collectors later on when jobs are loaded. 

Note that the `AsyncPeriodicWork` would kick off later on after `JOB_CONFIG_ADAPTED` https://github.com/jenkinsci/jenkins/blob/jenkins-2.468/core/src/main/java/hudson/model/PeriodicWork.java#L104.

### Changes proposed

- Prevent deadlock on initialization of `DefaultPrometheusMetrics`
- Remove Guice injection

### Checklist

- [ ] Includes tests covering the new functionality?
- [ ] Ready for review
- [ ] Follows CONTRIBUTING rules

### Notify

@Waschndolos


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..a0ea00b 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/rest/PrometheusAction.java
@@ -1,6 +1,5 @@
 package org.jenkinsci.plugins.prometheus.rest;
 
-import com.google.inject.Inject;
 import hudson.Extension;
 import hudson.model.UnprotectedRootAction;
 import hudson.util.HttpResponses;
@@ -8,6 +7,7 @@ import io.prometheus.client.exporter.common.TextFormat;
 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.kohsuke.stapler.HttpResponse;
 import org.kohsuke.stapler.StaplerRequest;
@@ -16,12 +16,7 @@ import org.kohsuke.stapler.StaplerResponse;
 @Extension
 public class PrometheusAction implements UnprotectedRootAction {
 
-    private PrometheusMetrics prometheusMetrics;
-
-    @Inject
-    public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) {
-        this.prometheusMetrics = prometheusMetrics;
-    }
+    private final PrometheusMetrics prometheusMetrics = DefaultPrometheusMetrics.get();
 
     @Override
     public String getIconFileName() {
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 9024c9c..1cf59d4 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/DefaultPrometheusMetrics.java
@@ -1,15 +1,24 @@
 package org.jenkinsci.plugins.prometheus.service;
 
+import edu.umd.cs.findbugs.annotations.NonNull;
 import hudson.ExtensionList;
+import hudson.init.InitMilestone;
+import hudson.init.Initializer;
 import io.prometheus.client.Collector;
 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 jenkins.metrics.api.Metrics;
-import org.jenkinsci.plugins.prometheus.*;
+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.config.disabledmetrics.FilteredMetricEnumeration;
 import org.jenkinsci.plugins.prometheus.util.JenkinsNodeBuildsSampleBuilder;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,27 +30,51 @@ public class DefaultPrometheusMetrics implements PrometheusMetrics {
 
     private static final Logger logger = LoggerFactory.getLogger(DefaultPrometheusMetrics.class);
 
+    private static DefaultPrometheusMetrics INSTANCE = null;
+
     private final CollectorRegistry collectorRegistry;
     private final AtomicReference<String> cachedMetrics;
 
-    public DefaultPrometheusMetrics() {
+    private DefaultPrometheusMetrics() {
         CollectorRegistry collectorRegistry = CollectorRegistry.defaultRegistry;
-        collectorRegistry.register(new JobCollector());
-        collectorRegistry.register(new JenkinsStatusCollector());
-        collectorRegistry.register(new DropwizardExports(Metrics.metricRegistry(), new JenkinsNodeBuildsSampleBuilder()));
-        collectorRegistry.register(new DiskUsageCollector());
-        collectorRegistry.register(new ExecutorCollector());
-        collectorRegistry.register(new CodeCoverageCollector());
-
-        // other collectors from other plugins
-        ExtensionList.lookup(Collector.class).forEach(collectorRegistry::register);
-
         DefaultExports.initialize();
-
         this.collectorRegistry = collectorRegistry;
         this.cachedMetrics = new AtomicReference<>("");
     }
 
+    public static synchronized DefaultPrometheusMetrics get() {
+        if(INSTANCE == null) {
+            INSTANCE = new DefaultPrometheusMetrics();
+        }
+        return INSTANCE;
+    }
+
+    @Restricted(NoExternalUse.class)
+    private void registerCollector(@NonNull Collector collector) {
+        collectorRegistry.register(collector);
+        logger.debug(String.format("Collector %s registered", collector.getClass().getName()));
+    }
+
+    @Restricted(NoExternalUse.class)
+    @Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED, before = InitMilestone.JOB_LOADED)
+    public static void registerCollectors() {
+        DefaultPrometheusMetrics instance = get();
+        instance.registerCollector(new JenkinsStatusCollector());
+        instance.registerCollector(new DropwizardExports(Metrics.metricRegistry(), new JenkinsNodeBuildsSampleBuilder()));
+        instance.registerCollector(new DiskUsageCollector());
+        instance.registerCollector(new ExecutorCollector());
+    }
+
+    @Restricted(NoExternalUse.class)
+    @Initializer(after = InitMilestone.JOB_LOADED, before = InitMilestone.JOB_CONFIG_ADAPTED)
+    public static void registerJobCollectors() {
+        DefaultPrometheusMetrics instance = get();
+        instance.registerCollector(new JobCollector());
+        instance.registerCollector(new CodeCoverageCollector());
+        // other collectors from other plugins
+        ExtensionList.lookup(Collector.class).forEach(instance::registerCollector);
+    }
+
     @Override
     public String getMetrics() {
         return cachedMetrics.get();
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..bbf7d29 100644
--- a/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java
+++ b/src/main/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorker.java
@@ -1,6 +1,5 @@
 package org.jenkinsci.plugins.prometheus.service;
 
-import com.google.inject.Inject;
 import hudson.Extension;
 import hudson.model.AsyncPeriodicWork;
 import hudson.model.TaskListener;
@@ -16,17 +15,12 @@ public class PrometheusAsyncWorker extends AsyncPeriodicWork {
 
     private static final Logger logger = LoggerFactory.getLogger(PrometheusAsyncWorker.class);
 
-    private PrometheusMetrics prometheusMetrics;
+    private final PrometheusMetrics prometheusMetrics = DefaultPrometheusMetrics.get();
 
     public PrometheusAsyncWorker() {
         super("prometheus_async_worker");
     }
 
-    @Inject
-    public void setPrometheusMetrics(PrometheusMetrics prometheusMetrics) {
-        this.prometheusMetrics = prometheusMetrics;
-    }
-
     @Override
     public long getRecurrencePeriod() {
         long collectingMetricsPeriodInMillis =


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..c50ce75 100644
--- a/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java
+++ b/src/test/java/org/jenkinsci/plugins/prometheus/rest/PrometheusActionTest.java
@@ -4,7 +4,7 @@ import io.prometheus.client.exporter.common.TextFormat;
 import jenkins.metrics.api.Metrics;
 import jenkins.model.Jenkins;
 import org.jenkinsci.plugins.prometheus.config.PrometheusConfiguration;
-import org.jenkinsci.plugins.prometheus.service.PrometheusMetrics;
+import org.jenkinsci.plugins.prometheus.service.DefaultPrometheusMetrics;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -22,8 +22,14 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
-import static java.net.HttpURLConnection.*;
-import static org.mockito.Mockito.*;
+import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
 public class PrometheusActionTest {
@@ -87,25 +93,27 @@ public class PrometheusActionTest {
     @Test
     public void shouldReturnMetrics() throws IOException, ServletException {
         // given
-        PrometheusAction action = new PrometheusAction();
-        PrometheusMetrics prometheusMetrics = mock(PrometheusMetrics.class);
+        DefaultPrometheusMetrics prometheusMetrics = mock(DefaultPrometheusMetrics.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);
-
-        // then
-        AssertStaplerResponse.from(actual)
-            .call()
-            .assertHttpStatus(HTTP_OK)
-            .assertContentType(TextFormat.CONTENT_TYPE_004)
-            .assertHttpHeader("Cache-Control", "must-revalidate,no-cache,no-store")
-            .assertBody(responseBody);
+        try (MockedStatic<DefaultPrometheusMetrics> defaultPrometheusMetricsMockedStatic = mockStatic(DefaultPrometheusMetrics.class)) {
+            defaultPrometheusMetricsMockedStatic.when(DefaultPrometheusMetrics::get).thenReturn(prometheusMetrics);
+            PrometheusAction action = new PrometheusAction();
+            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);
+        }
     }
 
     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..9fe678f 100644
--- a/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java
+++ b/src/test/java/org/jenkinsci/plugins/prometheus/service/PrometheusAsyncWorkerTest.java
@@ -5,29 +5,35 @@ import org.junit.jupiter.api.Test;
 import org.jvnet.hudson.test.Issue;
 import org.mockito.MockedStatic;
 
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.logging.Level;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 
 public class PrometheusAsyncWorkerTest {
 
     @Test
     public void shouldCollectMetrics() {
-        // 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<DefaultPrometheusMetrics> defaultPrometheusMetricsMockedStatic = mockStatic(DefaultPrometheusMetrics.class)) {
+            // given
+            DefaultPrometheusMetrics metrics = spy(DefaultPrometheusMetrics.class);
+            doNothing().when(metrics).collectMetrics();
+            defaultPrometheusMetricsMockedStatic.when(DefaultPrometheusMetrics::get).thenReturn(metrics);
+            PrometheusAsyncWorker asyncWorker = new PrometheusAsyncWorker();
+
+            // when
+            asyncWorker.execute(null);
+
+            // then
+            verify(metrics, times(1)).collectMetrics();
+        }
 
     }
     @Test
@@ -50,21 +56,4 @@ public class PrometheusAsyncWorkerTest {
         Level level = sut.getNormalLoggingLevel();
         assertEquals(Level.FINE, level);
     }
-
-    private static class TestPrometheusMetrics implements PrometheusMetrics {
-        private final AtomicReference<String> cachedMetrics = new AtomicReference<>("");
-        private final AtomicInteger counter = new AtomicInteger(0);
-
-        @Override
-        public String getMetrics() {
-            return cachedMetrics.get();
-        }
-
-        @Override
-        public void collectMetrics() {
-            String metrics = String.valueOf(counter.incrementAndGet());
-            cachedMetrics.set(metrics);
-        }
-
-    }
 }