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:
parent
d8d7e0a762
commit
697d5e6247
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void execute(Runnable task, long startTimeout) {
|
||||
execute(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable task) {
|
||||
FutureTask<Object> future = new FutureTask<>(task, null);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
|||
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 {
|
|||
|
||||
/**
|
||||
* 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 {
|
|||
* @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}
|
||||
|
|
|
@ -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
|
|||
/** 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
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
|||
* 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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void execute(Runnable task, long startTimeout) {
|
||||
execute(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable task) {
|
||||
try {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 "";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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…
Reference in New Issue