Skip to content

Commit 8da3dd7

Browse files
committed
Merge pull request #42407 from mturbe
* pr/42407: Polish "Add support for virtual threads in OtlpMetricRegistry configuration" Add support for virtual threads in OtlpMetricRegistry configuration Closes gh-42407
2 parents eb7b6a7 + 593d2cc commit 8da3dd7

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,9 +30,12 @@
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
34+
import org.springframework.boot.autoconfigure.thread.Threading;
3335
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3436
import org.springframework.context.annotation.Bean;
3537
import org.springframework.core.env.Environment;
38+
import org.springframework.core.task.VirtualThreadTaskExecutor;
3639

3740
/**
3841
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP.
@@ -72,10 +75,19 @@ OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties,
7275

7376
@Bean
7477
@ConditionalOnMissingBean
78+
@ConditionalOnThreading(Threading.PLATFORM)
7579
public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) {
7680
return new OtlpMeterRegistry(otlpConfig, clock);
7781
}
7882

83+
@Bean
84+
@ConditionalOnMissingBean
85+
@ConditionalOnThreading(Threading.VIRTUAL)
86+
public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock) {
87+
VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor("otlp-meter-registry-");
88+
return new OtlpMeterRegistry(otlpConfig, clock, taskExecutor.getVirtualThreadFactory());
89+
}
90+
7991
/**
8092
* Adapts {@link OtlpProperties} to {@link OtlpMetricsConnectionDetails}.
8193
*/

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
1818

19+
import java.util.concurrent.ScheduledExecutorService;
20+
1921
import io.micrometer.core.instrument.Clock;
2022
import io.micrometer.registry.otlp.OtlpConfig;
2123
import io.micrometer.registry.otlp.OtlpMeterRegistry;
2224
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.condition.EnabledForJreRange;
26+
import org.junit.jupiter.api.condition.JRE;
2327

2428
import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails;
2529
import org.springframework.boot.autoconfigure.AutoConfigurations;
2630
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
31+
import org.springframework.boot.testsupport.assertj.ScheduledExecutorServiceAssert;
2732
import org.springframework.context.annotation.Bean;
2833
import org.springframework.context.annotation.Configuration;
2934
import org.springframework.context.annotation.Import;
@@ -76,6 +81,32 @@ void allowsCustomConfigToBeUsed() {
7681
.hasBean("customConfig"));
7782
}
7883

84+
@Test
85+
void allowsPlatformThreadsToBeUsed() {
86+
this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> {
87+
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
88+
OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class);
89+
assertThat(registry).extracting("scheduledExecutorService")
90+
.satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor)
91+
.usesPlatformThreads());
92+
});
93+
}
94+
95+
@Test
96+
@EnabledForJreRange(min = JRE.JAVA_21)
97+
void allowsVirtualThreadsToBeUsed() {
98+
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
99+
.withPropertyValues("spring.threads.virtual.enabled=true")
100+
.run((context) -> {
101+
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
102+
OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class);
103+
assertThat(registry).extracting("scheduledExecutorService")
104+
.satisfies(
105+
(executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor)
106+
.usesVirtualThreads());
107+
});
108+
}
109+
79110
@Test
80111
void allowsRegistryToBeCustomized() {
81112
this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.testsupport.assertj;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.concurrent.ExecutionException;
21+
import java.util.concurrent.ScheduledExecutorService;
22+
import java.util.concurrent.ScheduledThreadPoolExecutor;
23+
import java.util.concurrent.TimeUnit;
24+
25+
import org.assertj.core.api.AbstractAssert;
26+
import org.assertj.core.api.Assert;
27+
28+
import org.springframework.util.ReflectionUtils;
29+
30+
/**
31+
* AssertJ {@link Assert} for {@link ScheduledThreadPoolExecutor}.
32+
*
33+
* @author Mike Turbe
34+
* @author Moritz Halbritter
35+
*/
36+
public final class ScheduledExecutorServiceAssert
37+
extends AbstractAssert<ScheduledExecutorServiceAssert, ScheduledExecutorService> {
38+
39+
private ScheduledExecutorServiceAssert(ScheduledExecutorService actual) {
40+
super(actual, ScheduledExecutorServiceAssert.class);
41+
}
42+
43+
/**
44+
* Verifies that the actual executor uses platform threads.
45+
* @return {@code this} assertion object
46+
* @throws AssertionError if the actual executor doesn't use platform threads
47+
*/
48+
public ScheduledExecutorServiceAssert usesPlatformThreads() {
49+
isNotNull();
50+
if (producesVirtualThreads()) {
51+
failWithMessage("Expected executor to use platform threads, but it uses virtual threads");
52+
}
53+
return this;
54+
}
55+
56+
/**
57+
* Verifies that the actual executor uses virtual threads.
58+
* @return {@code this} assertion object
59+
* @throws AssertionError if the actual executor doesn't use virtual threads
60+
*/
61+
public ScheduledExecutorServiceAssert usesVirtualThreads() {
62+
isNotNull();
63+
if (!producesVirtualThreads()) {
64+
failWithMessage("Expected executor to use virtual threads, but it uses platform threads");
65+
}
66+
return this;
67+
}
68+
69+
private boolean producesVirtualThreads() {
70+
try {
71+
return this.actual.schedule(() -> {
72+
Method isVirtual = ReflectionUtils.findMethod(Thread.class, "isVirtual");
73+
if (isVirtual == null) {
74+
return false;
75+
}
76+
return (boolean) ReflectionUtils.invokeMethod(isVirtual, Thread.currentThread());
77+
}, 0, TimeUnit.SECONDS).get();
78+
}
79+
catch (InterruptedException | ExecutionException ex) {
80+
throw new AssertionError(ex);
81+
}
82+
}
83+
84+
/**
85+
* Creates a new assertion class with the given {@link ScheduledExecutorService}.
86+
* @param actual the {@link ScheduledExecutorService}
87+
* @return the assertion class
88+
*/
89+
public static ScheduledExecutorServiceAssert assertThat(ScheduledExecutorService actual) {
90+
return new ScheduledExecutorServiceAssert(actual);
91+
}
92+
93+
}

0 commit comments

Comments
 (0)