Browse Source

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
pull/30456/head
Juergen Hoeller 1 year ago
parent
commit
697d5e6247
  1. 1
      build.gradle
  2. 2
      ci/images/get-jdk-url.sh
  3. 8
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java
  4. 6
      spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java
  5. 6
      spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java
  6. 10
      spring-core/spring-core.gradle
  7. 25
      spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java
  8. 20
      spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
  9. 47
      spring-core/src/main/java/org/springframework/core/task/VirtualThreadDelegate.java
  10. 67
      spring-core/src/main/java/org/springframework/core/task/VirtualThreadTaskExecutor.java
  11. 8
      spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
  12. 45
      spring-core/src/main/java21/org/springframework/core/task/VirtualThreadDelegate.java
  13. 2
      spring-core/src/test/java/org/springframework/core/io/ClassPathResourceTests.java
  14. 54
      spring-core/src/test/java/org/springframework/core/io/ResourceTests.java
  15. 142
      spring-core/src/test/java21/org/springframework/core/task/VirtualThreadTaskExecutorTests.java
  16. 6
      src/checkstyle/checkstyle-suppressions.xml

1
build.gradle

@ -10,6 +10,7 @@ plugins { @@ -10,6 +10,7 @@ plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.0' apply false
id 'me.champeau.mrjar' version '0.1.1'
}
ext {

2
ci/images/get-jdk-url.sh

@ -9,7 +9,7 @@ case "$1" in @@ -9,7 +9,7 @@ case "$1" in
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
;;
java21)
echo "https://download.java.net/java/early_access/jdk21/18/GPL/openjdk-21-ea+18_linux-x64_bin.tar.gz"
echo "https://download.java.net/java/early_access/jdk21/20/GPL/openjdk-21-ea+20_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"

8
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -77,12 +77,6 @@ public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool @@ -77,12 +77,6 @@ public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool
}
}
@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
@Override
public Future<?> submit(Runnable task) {
FutureTask<Object> future = new FutureTask<>(task, null);

6
spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java

@ -364,12 +364,6 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport @@ -364,12 +364,6 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport
}
}
@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
@Override
public Future<?> submit(Runnable task) {
ExecutorService executor = getThreadPoolExecutor();

6
spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java

@ -282,12 +282,6 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @@ -282,12 +282,6 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
}
}
@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
@Override
public Future<?> submit(Runnable task) {
ExecutorService executor = getScheduledExecutor();

10
spring-core/spring-core.gradle

@ -1,15 +1,25 @@ @@ -1,15 +1,25 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.springframework.build.shadow.ShadowSource
plugins {
id 'me.champeau.mrjar'
}
description = "Spring Core"
apply plugin: "kotlin"
apply plugin: "kotlinx-serialization"
multiRelease {
targetVersions 17, 21
}
def javapoetVersion = "1.13.0"
def objenesisVersion = "3.3"
configurations {
java21Api.extendsFrom(api)
java21Implementation.extendsFrom(implementation)
javapoet
objenesis
graalvm

25
spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -19,6 +19,7 @@ package org.springframework.core.task; @@ -19,6 +19,7 @@ package org.springframework.core.task;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.springframework.util.concurrent.FutureUtils;
@ -60,6 +61,8 @@ public interface AsyncTaskExecutor extends TaskExecutor { @@ -60,6 +61,8 @@ public interface AsyncTaskExecutor extends TaskExecutor {
/**
* Execute the given {@code task}.
* <p>As of 6.1, this method comes with a default implementation that simply
* delegates to {@link #execute(Runnable)}, ignoring the timeout completely.
* @param task the {@code Runnable} to execute (never {@code null})
* @param startTimeout the time duration (milliseconds) within which the task is
* supposed to start. This is intended as a hint to the executor, allowing for
@ -72,27 +75,41 @@ public interface AsyncTaskExecutor extends TaskExecutor { @@ -72,27 +75,41 @@ public interface AsyncTaskExecutor extends TaskExecutor {
* @deprecated as of 5.3.16 since the common executors do not support start timeouts
*/
@Deprecated
void execute(Runnable task, long startTimeout);
default void execute(Runnable task, long startTimeout) {
execute(task);
}
/**
* Submit a Runnable task for execution, receiving a Future representing that task.
* The Future will return a {@code null} result upon completion.
* <p>As of 6.1, this method comes with a default implementation that delegates
* to {@link #execute(Runnable)}.
* @param task the {@code Runnable} to execute (never {@code null})
* @return a Future representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @since 3.0
*/
Future<?> submit(Runnable task);
default Future<?> submit(Runnable task) {
FutureTask<Object> future = new FutureTask<>(task, null);
execute(future);
return future;
}
/**
* Submit a Callable task for execution, receiving a Future representing that task.
* The Future will return the Callable's result upon completion.
* <p>As of 6.1, this method comes with a default implementation that delegates
* to {@link #execute(Runnable)}.
* @param task the {@code Callable} to execute (never {@code null})
* @return a Future representing pending completion of the task
* @throws TaskRejectedException if the given task was not accepted
* @since 3.0
*/
<T> Future<T> submit(Callable<T> task);
default <T> Future<T> submit(Callable<T> task) {
FutureTask<T> future = new FutureTask<>(task);
execute(future, TIMEOUT_INDEFINITE);
return future;
}
/**
* Submit a {@code Runnable} task for execution, receiving a {@code CompletableFuture}

20
spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -66,6 +66,9 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator @@ -66,6 +66,9 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
/** Internal concurrency throttle used by this executor. */
private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();
@Nullable
private VirtualThreadDelegate virtualThreadDelegate;
@Nullable
private ThreadFactory threadFactory;
@ -97,6 +100,16 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator @@ -97,6 +100,16 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
}
/**
* Switch this executor to virtual threads. Requires Java 21 or higher.
* <p>The default is {@code false}, indicating platform threads.
* Set this flag to {@code true} in order to create virtual threads instead.
* @since 6.1
*/
public void setVirtualThreads(boolean virtual) {
this.virtualThreadDelegate = (virtual ? new VirtualThreadDelegate() : null);
}
/**
* Specify an external factory to use for creating new Threads,
* instead of relying on the local properties of this executor.
@ -238,11 +251,16 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator @@ -238,11 +251,16 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator
* Template method for the actual execution of a task.
* <p>The default implementation creates a new Thread and starts it.
* @param task the Runnable to execute
* @see #setVirtualThreads
* @see #setThreadFactory
* @see #createThread
* @see java.lang.Thread#start()
*/
protected void doExecute(Runnable task) {
if (this.virtualThreadDelegate != null) {
this.virtualThreadDelegate.startVirtualThread(nextThreadName(), task);
}
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}

47
spring-core/src/main/java/org/springframework/core/task/VirtualThreadDelegate.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* 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 java.util.concurrent.ThreadFactory;
/**
* Internal delegate for virtual thread handling on JDK 21.
* This is a dummy version for reachability on JDK <21.
*
* @author Juergen Hoeller
* @since 6.1
* @see VirtualThreadTaskExecutor
*/
class VirtualThreadDelegate {
public VirtualThreadDelegate() {
throw new UnsupportedOperationException("Virtual threads not supported on JDK <21");
}
public ThreadFactory virtualThreadFactory() {
throw new UnsupportedOperationException();
}
public ThreadFactory virtualThreadFactory(String threadNamePrefix) {
throw new UnsupportedOperationException();
}
public Thread startVirtualThread(String name, Runnable task) {
throw new UnsupportedOperationException();
}
}

67
spring-core/src/main/java/org/springframework/core/task/VirtualThreadTaskExecutor.java

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
/*
* 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 java.util.concurrent.ThreadFactory;
/**
* A {@link TaskExecutor} implementation based on virtual threads in JDK 21+.
* The only configuration option is a thread name prefix.
*
* <p>For additional features such as concurrency limiting or task decoration,
* consider using {@link SimpleAsyncTaskExecutor#setVirtualThreads} instead.
*
* @author Juergen Hoeller
* @since 6.1
* @see SimpleAsyncTaskExecutor
*/
public class VirtualThreadTaskExecutor implements AsyncTaskExecutor {
private final ThreadFactory virtualThreadFactory;
/**
* Create a new {@code VirtualThreadTaskExecutor} without thread naming.
*/
public VirtualThreadTaskExecutor() {
this.virtualThreadFactory = new VirtualThreadDelegate().virtualThreadFactory();
}
/**
* Create a new {@code VirtualThreadTaskExecutor} with thread names based
* on the given thread name prefix followed by a counter (e.g. "test-0").
* @param threadNamePrefix the prefix for thread names (e.g. "test-")
*/
public VirtualThreadTaskExecutor(String threadNamePrefix) {
this.virtualThreadFactory = new VirtualThreadDelegate().virtualThreadFactory(threadNamePrefix);
}
/**
* Return the underlying virtual {@link ThreadFactory}.
* Can also be used for custom thread creation elsewhere.
*/
public final ThreadFactory getVirtualThreadFactory() {
return this.virtualThreadFactory;
}
@Override
public void execute(Runnable task) {
this.virtualThreadFactory.newThread(task).start();
}
}

8
spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@ -98,12 +98,6 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor { @@ -98,12 +98,6 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
}
}
@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
@Override
public Future<?> submit(Runnable task) {
try {

45
spring-core/src/main/java21/org/springframework/core/task/VirtualThreadDelegate.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* 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 java.util.concurrent.ThreadFactory;
/**
* Internal delegate for virtual thread handling on JDK 21.
* This is the actual version compiled against JDK 21.
*
* @author Juergen Hoeller
* @since 6.1
* @see VirtualThreadTaskExecutor
*/
class VirtualThreadDelegate {
private final Thread.Builder threadBuilder = Thread.ofVirtual();
public ThreadFactory virtualThreadFactory() {
return this.threadBuilder.factory();
}
public ThreadFactory virtualThreadFactory(String threadNamePrefix) {
return this.threadBuilder.name(threadNamePrefix, 0).factory();
}
public Thread startVirtualThread(String name, Runnable task) {
return this.threadBuilder.name(name).start(task);
}
}

2
spring-core/src/test/java/org/springframework/core/io/ClassPathResourceTests.java

@ -214,7 +214,7 @@ class ClassPathResourceTests { @@ -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();

54
spring-core/src/test/java/org/springframework/core/io/ResourceTests.java

@ -68,8 +68,8 @@ class ResourceTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -459,7 +458,6 @@ class ResourceTests {
public InputStream getInputStream() {
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
}
@Override
public String getDescription() {
return "";

142
spring-core/src/test/java21/org/springframework/core/task/VirtualThreadTaskExecutorTests.java

@ -0,0 +1,142 @@ @@ -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();
}
}
}

6
src/checkstyle/checkstyle-suppressions.xml

@ -11,9 +11,9 @@ @@ -11,9 +11,9 @@
<suppress files="(^(?!.+[\\/]src[\\/]main[\\/]java[\\/].*package-info\.java))|(.*framework-docs.*)|(.*spring-(context-indexer|instrument|jcl).*)" checks="RegexpSinglelineJava" id="packageLevelNonNullFieldsAnnotation" />
<!-- Global: tests and test fixtures -->
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]" checks="AnnotationLocation|AnnotationUseStyle|AtclauseOrder|AvoidNestedBlocks|FinalClass|HideUtilityClassConstructor|InnerTypeLast|JavadocStyle|JavadocType|JavadocVariable|LeftCurly|MultipleVariableDeclarations|NeedBraces|OneTopLevelClass|OuterTypeFilename|RequireThis|SpringCatch|SpringJavadoc|SpringNoThis"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]org[\\/]springframework[\\/].+(Tests|Suite)" checks="IllegalImport" id="bannedJUnitJupiterImports"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]" checks="SpringJUnit5" message="should not be public"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]" checks="AnnotationLocation|AnnotationUseStyle|AtclauseOrder|AvoidNestedBlocks|FinalClass|HideUtilityClassConstructor|InnerTypeLast|JavadocStyle|JavadocType|JavadocVariable|LeftCurly|MultipleVariableDeclarations|NeedBraces|OneTopLevelClass|OuterTypeFilename|RequireThis|SpringCatch|SpringJavadoc|SpringNoThis"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]org[\\/]springframework[\\/].+(Tests|Suite)" checks="IllegalImport" id="bannedJUnitJupiterImports"/> <suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]org[\\/]springframework[\\/].+(Tests|Suite)" checks="IllegalImport" id="bannedJUnitJupiterImports"/>
<suppress files="[\\/]src[\\/](test|testFixtures)[\\/]java[\\/]" checks="SpringJUnit5" message="should not be public"/> <suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]" checks="SpringJUnit5" message="should not be public"/>
<!-- JMH benchmarks -->
<suppress files="[\\/]src[\\/]jmh[\\/]java[\\/]org[\\/]springframework[\\/]" checks="JavadocVariable|JavadocStyle|InnerTypeLast"/>

Loading…
Cancel
Save