Configure Virtual Threads support in Reactor

This commit configures the virtual threads support in Reactor Core for
Schedulers if:
* the current JDK is 21 or higher
* the current environment enables this globally with the
  `"spring.threads.virtual.enabled"` property.

This needs to happen early in the application startup process, as this
feature is detected statically when the first schedulers call is made.
As a result, this is being done with an environment post processor and
not with an auto-configuration class.

Closes gh-36302
This commit is contained in:
Brian Clozel 2023-08-21 16:43:41 +02:00
parent 1798c561f4
commit d46a58f0f6
5 changed files with 47 additions and 20 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,31 +18,34 @@ package org.springframework.boot.reactor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.system.JavaVersion;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ClassUtils;
/**
* {@link EnvironmentPostProcessor} to enable the Reactor Debug Agent if available.
* {@link EnvironmentPostProcessor} to enable the Reactor global features as early as
* possible in the startup process.
* <p>
* The debug agent is enabled by default, unless the
* {@code "spring.reactor.debug-agent.enabled"} configuration property is set to false. We
* are using here an {@link EnvironmentPostProcessor} instead of an auto-configuration
* class to enable the agent as soon as possible during the startup process.
* If the "reactor-tools" dependency is available, the debug agent is enabled by default,
* unless the {@code "spring.reactor.debug-agent.enabled"} configuration property is set
* to false.
* <p>
* If the {@code "spring.threads.virtual.enabled"} property is enabled and the current JVM
* is 21 or later, then the Reactor System property is set to configure the Bounded
* Elastic Scheduler to use Virtual Threads globally.
*
* @author Brian Clozel
* @since 2.2.0
* @since 3.2.0
*/
public class DebugAgentEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
public class ReactorEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final String REACTOR_DEBUGAGENT_CLASS = "reactor.tools.agent.ReactorDebugAgent";
private static final String DEBUGAGENT_ENABLED_CONFIG_KEY = "spring.reactor.debug-agent.enabled";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (ClassUtils.isPresent(REACTOR_DEBUGAGENT_CLASS, null)) {
Boolean agentEnabled = environment.getProperty(DEBUGAGENT_ENABLED_CONFIG_KEY, Boolean.class);
Boolean agentEnabled = environment.getProperty("spring.reactor.debug-agent.enabled", Boolean.class);
if (agentEnabled != Boolean.FALSE) {
try {
Class<?> debugAgent = Class.forName(REACTOR_DEBUGAGENT_CLASS);
@ -53,6 +56,10 @@ public class DebugAgentEnvironmentPostProcessor implements EnvironmentPostProces
}
}
}
if (environment.getProperty("spring.threads.virtual.enabled", boolean.class, false)
&& JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.TWENTY_ONE)) {
System.setProperty("reactor.schedulers.defaultBoundedElasticOnVirtualThreads", "true");
}
}
@Override

View File

@ -491,7 +491,7 @@
{
"name": "spring.reactor.debug-agent.enabled",
"type": "java.lang.Boolean",
"sourceType": "org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor",
"sourceType": "org.springframework.boot.reactor.ReactorEnvironmentPostProcessor",
"description": "Whether the Reactor Debug Agent should be enabled when reactor-tools is present.",
"defaultValue": true
},

View File

@ -57,7 +57,7 @@ org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
org.springframework.boot.reactor.ReactorEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\

View File

@ -22,7 +22,7 @@ import reactor.core.publisher.Flux;
* Utility class that should be instrumented by the reactor debug agent.
*
* @author Brian Clozel
* @see DebugAgentEnvironmentPostProcessorTests
* @see ReactorEnvironmentPostProcessorTests
*/
class InstrumentedFluxProvider {

View File

@ -16,8 +16,10 @@
package org.springframework.boot.reactor;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import reactor.core.Scannable;
import reactor.core.publisher.Flux;
@ -27,17 +29,18 @@ import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DebugAgentEnvironmentPostProcessor}.
* Tests for {@link ReactorEnvironmentPostProcessor}.
*
* @author Brian Clozel
*/
@Disabled("We need the not-yet-released reactor-tools 3.4.11 for JDK 17 compatibility")
@ClassPathOverrides("io.projectreactor:reactor-tools:3.4.11")
class DebugAgentEnvironmentPostProcessorTests {
@ClassPathOverrides("io.projectreactor:reactor-tools:3.5.9")
class ReactorEnvironmentPostProcessorTests {
static {
MockEnvironment environment = new MockEnvironment();
DebugAgentEnvironmentPostProcessor postProcessor = new DebugAgentEnvironmentPostProcessor();
environment.setProperty("spring.threads.virtual.enabled", "true");
ReactorEnvironmentPostProcessor postProcessor = new ReactorEnvironmentPostProcessor();
postProcessor.postProcessEnvironment(environment, null);
}
@ -49,4 +52,21 @@ class DebugAgentEnvironmentPostProcessorTests {
.startsWith("Flux.just ⇢ at org.springframework.boot.reactor.InstrumentedFluxProvider.newFluxJust");
}
@Test
@EnabledForJreRange(max = JRE.JAVA_20)
void shouldNotEnableVirtualThreads() {
assertThat(System.getProperty("reactor.schedulers.defaultBoundedElasticOnVirtualThreads")).isNotEqualTo("true");
}
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void shouldEnableVirtualThreads() {
assertThat(System.getProperty("reactor.schedulers.defaultBoundedElasticOnVirtualThreads")).isEqualTo("true");
}
@AfterEach
void cleanup() {
System.setProperty("reactor.schedulers.defaultBoundedElasticOnVirtualThreads", "false");
}
}