Configuration options for virtual threads (on JDK 21)

VirtualThreadDelegate built on JDK 21 for multi-release jar.
Includes dedicated VirtualThreadTaskExecutor as lean option.
Includes setVirtualThreads flag on SimpleAsyncTaskExecutor.
Includes additional default methods on AsyncTaskExecutor.

Closes gh-30241
This commit is contained in:
Juergen Hoeller
2023-05-08 11:22:47 +02:00
parent d8d7e0a762
commit 697d5e6247
16 changed files with 385 additions and 64 deletions

View File

@@ -214,7 +214,7 @@ class ClassPathResourceTests {
@Test
void directoryNotReadable() throws Exception {
Resource fileDir = new ClassPathResource("org/springframework/core");
Resource fileDir = new ClassPathResource("example/type");
assertThat(fileDir.getURL()).asString().startsWith("file:");
assertThat(fileDir.exists()).isTrue();
assertThat(fileDir.isReadable()).isFalse();

View File

@@ -68,8 +68,8 @@ class ResourceTests {
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceIsValid(Resource resource) throws Exception {
assertThat(resource.getFilename()).isEqualTo("Resource.class");
assertThat(resource.getURL().getFile()).endsWith("Resource.class");
assertThat(resource.getFilename()).isEqualTo("ResourceTests.class");
assertThat(resource.getURL().getFile()).endsWith("ResourceTests.class");
assertThat(resource.exists()).isTrue();
assertThat(resource.isReadable()).isTrue();
assertThat(resource.contentLength()).isGreaterThan(0);
@@ -80,9 +80,9 @@ class ResourceTests {
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelative(Resource resource) throws Exception {
Resource relative1 = resource.createRelative("ClassPathResource.class");
assertThat(relative1.getFilename()).isEqualTo("ClassPathResource.class");
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
Resource relative1 = resource.createRelative("ClassPathResourceTests.class");
assertThat(relative1.getFilename()).isEqualTo("ClassPathResourceTests.class");
assertThat(relative1.getURL().getFile().endsWith("ClassPathResourceTests.class")).isTrue();
assertThat(relative1.exists()).isTrue();
assertThat(relative1.isReadable()).isTrue();
assertThat(relative1.contentLength()).isGreaterThan(0);
@@ -92,9 +92,9 @@ class ResourceTests {
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
assertThat(relative2.getURL().getFile()).endsWith("ResourcePatternResolver.class");
Resource relative2 = resource.createRelative("support/PathMatchingResourcePatternResolverTests.class");
assertThat(relative2.getFilename()).isEqualTo("PathMatchingResourcePatternResolverTests.class");
assertThat(relative2.getURL().getFile()).endsWith("PathMatchingResourcePatternResolverTests.class");
assertThat(relative2.exists()).isTrue();
assertThat(relative2.isReadable()).isTrue();
assertThat(relative2.contentLength()).isGreaterThan(0);
@@ -104,9 +104,9 @@ class ResourceTests {
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("resource")
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
Resource relative3 = resource.createRelative("../SpringVersion.class");
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
assertThat(relative3.getURL().getFile()).endsWith("SpringVersion.class");
Resource relative3 = resource.createRelative("../CollectionFactoryTests.class");
assertThat(relative3.getFilename()).isEqualTo("CollectionFactoryTests.class");
assertThat(relative3.getURL().getFile()).endsWith("CollectionFactoryTests.class");
assertThat(relative3.exists()).isTrue();
assertThat(relative3.isReadable()).isTrue();
assertThat(relative3.contentLength()).isGreaterThan(0);
@@ -128,12 +128,12 @@ class ResourceTests {
}
private static Stream<Arguments> resource() throws URISyntaxException {
URL resourceClass = ResourceTests.class.getResource("Resource.class");
URL resourceClass = ResourceTests.class.getResource("ResourceTests.class");
Path resourceClassFilePath = Paths.get(resourceClass.toURI());
return Stream.of(
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
arguments(named("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/ResourceTests.class"))),
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/ResourceTests.class", ResourceTests.class.getClassLoader()))),
arguments(named("ClassPathResource with Class", new ClassPathResource("ResourceTests.class", ResourceTests.class))),
arguments(named("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
arguments(named("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
arguments(named("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
@@ -220,29 +220,29 @@ class ResourceTests {
@Test
void sameResourceIsEqual() {
String file = getClass().getResource("Resource.class").getFile();
String file = getClass().getResource("ResourceTests.class").getFile();
Resource resource = new FileSystemResource(file);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void sameResourceFromFileIsEqual() {
File file = new File(getClass().getResource("Resource.class").getFile());
File file = new File(getClass().getResource("ResourceTests.class").getFile());
Resource resource = new FileSystemResource(file);
assertThat(resource).isEqualTo(new FileSystemResource(file));
}
@Test
void sameResourceFromFilePathIsEqual() throws Exception {
Path filePath = Paths.get(getClass().getResource("Resource.class").toURI());
Path filePath = Paths.get(getClass().getResource("ResourceTests.class").toURI());
Resource resource = new FileSystemResource(filePath);
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
}
@Test
void sameResourceFromDotPathIsEqual() {
Resource resource = new FileSystemResource("core/io/Resource.class");
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
Resource resource = new FileSystemResource("core/io/ResourceTests.class");
assertThat(new FileSystemResource("core/../core/io/./ResourceTests.class")).isEqualTo(resource);
}
@Test
@@ -254,7 +254,7 @@ class ResourceTests {
@Test
void readableChannelProvidesContent() throws Exception {
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
Resource resource = new FileSystemResource(getClass().getResource("ResourceTests.class").getFile());
try (ReadableByteChannel channel = resource.readableChannel()) {
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer);
@@ -293,8 +293,8 @@ class ResourceTests {
@Test
void sameResourceWithRelativePathIsEqual() throws Exception {
Resource resource = new UrlResource("file:core/io/Resource.class");
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource);
Resource resource = new UrlResource("file:core/io/ResourceTests.class");
assertThat(new UrlResource("file:core/../core/io/./ResourceTests.class")).isEqualTo(resource);
}
@Test
@@ -322,14 +322,14 @@ class ResourceTests {
@Test
void factoryMethodsProduceEqualResources() throws Exception {
Resource resource1 = new UrlResource("file:core/io/Resource.class");
Resource resource2 = UrlResource.from("file:core/io/Resource.class");
Resource resource1 = new UrlResource("file:core/io/ResourceTests.class");
Resource resource2 = UrlResource.from("file:core/io/ResourceTests.class");
Resource resource3 = UrlResource.from(resource1.getURI());
assertThat(resource2.getURL()).isEqualTo(resource1.getURL());
assertThat(resource3.getURL()).isEqualTo(resource1.getURL());
assertThat(UrlResource.from("file:core/../core/io/./Resource.class")).isEqualTo(resource1);
assertThat(UrlResource.from("file:core/../core/io/./ResourceTests.class")).isEqualTo(resource1);
assertThat(UrlResource.from("file:/dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir\\test.txt?argh").getFilename()).isEqualTo("test.txt");
assertThat(UrlResource.from("file:\\dir/test.txt?argh").getFilename()).isEqualTo("test.txt");
@@ -433,7 +433,6 @@ class ResourceTests {
public String getDescription() {
return name;
}
@Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException();
@@ -459,7 +458,6 @@ class ResourceTests {
public InputStream getInputStream() {
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
}
@Override
public String getDescription() {
return "";

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2002-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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.task;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Juergen Hoeller
* @since 6.1
*/
class VirtualThreadTaskExecutorTests {
@Test
void virtualThreadsWithoutName() {
final Object monitor = new Object();
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor();
ThreadNameHarvester task = new ThreadNameHarvester(monitor);
executeAndWait(executor, task, monitor);
assertThat(task.getThreadName()).isEmpty();
assertThat(task.isVirtual()).isTrue();
}
@Test
void virtualThreadsWithNamePrefix() {
final Object monitor = new Object();
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("test-");
ThreadNameHarvester task = new ThreadNameHarvester(monitor);
executeAndWait(executor, task, monitor);
assertThat(task.getThreadName()).isEqualTo("test-0");
assertThat(task.isVirtual()).isTrue();
}
@Test
void simpleWithVirtualThreadFactory() {
final Object monitor = new Object();
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(Thread.ofVirtual().name("test").factory());
ThreadNameHarvester task = new ThreadNameHarvester(monitor);
executeAndWait(executor, task, monitor);
assertThat(task.getThreadName()).isEqualTo("test");
assertThat(task.isVirtual()).isTrue();
}
@Test
void simpleWithVirtualThreadFlag() {
final String customPrefix = "chankPop#";
final Object monitor = new Object();
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(customPrefix);
executor.setVirtualThreads(true);
ThreadNameHarvester task = new ThreadNameHarvester(monitor);
executeAndWait(executor, task, monitor);
assertThat(task.getThreadName()).startsWith(customPrefix);
assertThat(task.isVirtual()).isTrue();
}
private void executeAndWait(TaskExecutor executor, Runnable task, Object monitor) {
synchronized (monitor) {
executor.execute(task);
try {
monitor.wait();
}
catch (InterruptedException ignored) {
}
}
}
private static final class NoOpRunnable implements Runnable {
@Override
public void run() {
// no-op
}
}
private static abstract class AbstractNotifyingRunnable implements Runnable {
private final Object monitor;
protected AbstractNotifyingRunnable(Object monitor) {
this.monitor = monitor;
}
@Override
public final void run() {
synchronized (this.monitor) {
try {
doRun();
}
finally {
this.monitor.notifyAll();
}
}
}
protected abstract void doRun();
}
private static final class ThreadNameHarvester extends AbstractNotifyingRunnable {
private String threadName;
private boolean virtual;
protected ThreadNameHarvester(Object monitor) {
super(monitor);
}
public String getThreadName() {
return this.threadName;
}
public boolean isVirtual() {
return this.virtual;
}
@Override
protected void doRun() {
Thread thread = Thread.currentThread();
this.threadName = thread.getName();
this.virtual = thread.isVirtual();
}
}
}