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 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||||
id 'de.undercouch.download' version '5.4.0'
|
id 'de.undercouch.download' version '5.4.0'
|
||||||
id 'me.champeau.jmh' version '0.7.0' apply false
|
id 'me.champeau.jmh' version '0.7.0' apply false
|
||||||
|
id 'me.champeau.mrjar' version '0.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
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"
|
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
|
||||||
;;
|
;;
|
||||||
java21)
|
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"
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
@Override
|
||||||
public Future<?> submit(Runnable task) {
|
public Future<?> submit(Runnable task) {
|
||||||
FutureTask<Object> future = new FutureTask<>(task, null);
|
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
|
@Override
|
||||||
public Future<?> submit(Runnable task) {
|
public Future<?> submit(Runnable task) {
|
||||||
ExecutorService executor = getThreadPoolExecutor();
|
ExecutorService executor = getThreadPoolExecutor();
|
||||||
|
|
|
||||||
|
|
@ -282,12 +282,6 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public void execute(Runnable task, long startTimeout) {
|
|
||||||
execute(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Future<?> submit(Runnable task) {
|
public Future<?> submit(Runnable task) {
|
||||||
ExecutorService executor = getScheduledExecutor();
|
ExecutorService executor = getScheduledExecutor();
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
import org.springframework.build.shadow.ShadowSource
|
import org.springframework.build.shadow.ShadowSource
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'me.champeau.mrjar'
|
||||||
|
}
|
||||||
|
|
||||||
description = "Spring Core"
|
description = "Spring Core"
|
||||||
|
|
||||||
apply plugin: "kotlin"
|
apply plugin: "kotlin"
|
||||||
apply plugin: "kotlinx-serialization"
|
apply plugin: "kotlinx-serialization"
|
||||||
|
|
||||||
|
multiRelease {
|
||||||
|
targetVersions 17, 21
|
||||||
|
}
|
||||||
|
|
||||||
def javapoetVersion = "1.13.0"
|
def javapoetVersion = "1.13.0"
|
||||||
def objenesisVersion = "3.3"
|
def objenesisVersion = "3.3"
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
java21Api.extendsFrom(api)
|
||||||
|
java21Implementation.extendsFrom(implementation)
|
||||||
javapoet
|
javapoet
|
||||||
objenesis
|
objenesis
|
||||||
graalvm
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Callable;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
|
||||||
import org.springframework.util.concurrent.FutureUtils;
|
import org.springframework.util.concurrent.FutureUtils;
|
||||||
|
|
||||||
|
|
@ -60,6 +61,8 @@ public interface AsyncTaskExecutor extends TaskExecutor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the given {@code task}.
|
* 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 task the {@code Runnable} to execute (never {@code null})
|
||||||
* @param startTimeout the time duration (milliseconds) within which the task is
|
* @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
|
* 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 as of 5.3.16 since the common executors do not support start timeouts
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@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.
|
* Submit a Runnable task for execution, receiving a Future representing that task.
|
||||||
* The Future will return a {@code null} result upon completion.
|
* 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})
|
* @param task the {@code Runnable} to execute (never {@code null})
|
||||||
* @return a Future representing pending completion of the task
|
* @return a Future representing pending completion of the task
|
||||||
* @throws TaskRejectedException if the given task was not accepted
|
* @throws TaskRejectedException if the given task was not accepted
|
||||||
* @since 3.0
|
* @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.
|
* Submit a Callable task for execution, receiving a Future representing that task.
|
||||||
* The Future will return the Callable's result upon completion.
|
* 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})
|
* @param task the {@code Callable} to execute (never {@code null})
|
||||||
* @return a Future representing pending completion of the task
|
* @return a Future representing pending completion of the task
|
||||||
* @throws TaskRejectedException if the given task was not accepted
|
* @throws TaskRejectedException if the given task was not accepted
|
||||||
* @since 3.0
|
* @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}
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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. */
|
/** Internal concurrency throttle used by this executor. */
|
||||||
private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();
|
private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private VirtualThreadDelegate virtualThreadDelegate;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ThreadFactory threadFactory;
|
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,
|
* Specify an external factory to use for creating new Threads,
|
||||||
* instead of relying on the local properties of this executor.
|
* 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.
|
* Template method for the actual execution of a task.
|
||||||
* <p>The default implementation creates a new Thread and starts it.
|
* <p>The default implementation creates a new Thread and starts it.
|
||||||
* @param task the Runnable to execute
|
* @param task the Runnable to execute
|
||||||
|
* @see #setVirtualThreads
|
||||||
* @see #setThreadFactory
|
* @see #setThreadFactory
|
||||||
* @see #createThread
|
* @see #createThread
|
||||||
* @see java.lang.Thread#start()
|
* @see java.lang.Thread#start()
|
||||||
*/
|
*/
|
||||||
protected void doExecute(Runnable task) {
|
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 thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
|
||||||
thread.start();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
@Override
|
||||||
public Future<?> submit(Runnable task) {
|
public Future<?> submit(Runnable task) {
|
||||||
try {
|
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
|
@Test
|
||||||
void directoryNotReadable() throws Exception {
|
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.getURL()).asString().startsWith("file:");
|
||||||
assertThat(fileDir.exists()).isTrue();
|
assertThat(fileDir.exists()).isTrue();
|
||||||
assertThat(fileDir.isReadable()).isFalse();
|
assertThat(fileDir.isReadable()).isFalse();
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,8 @@ class ResourceTests {
|
||||||
@ParameterizedTest(name = "{index}: {0}")
|
@ParameterizedTest(name = "{index}: {0}")
|
||||||
@MethodSource("resource")
|
@MethodSource("resource")
|
||||||
void resourceIsValid(Resource resource) throws Exception {
|
void resourceIsValid(Resource resource) throws Exception {
|
||||||
assertThat(resource.getFilename()).isEqualTo("Resource.class");
|
assertThat(resource.getFilename()).isEqualTo("ResourceTests.class");
|
||||||
assertThat(resource.getURL().getFile()).endsWith("Resource.class");
|
assertThat(resource.getURL().getFile()).endsWith("ResourceTests.class");
|
||||||
assertThat(resource.exists()).isTrue();
|
assertThat(resource.exists()).isTrue();
|
||||||
assertThat(resource.isReadable()).isTrue();
|
assertThat(resource.isReadable()).isTrue();
|
||||||
assertThat(resource.contentLength()).isGreaterThan(0);
|
assertThat(resource.contentLength()).isGreaterThan(0);
|
||||||
|
|
@ -80,9 +80,9 @@ class ResourceTests {
|
||||||
@ParameterizedTest(name = "{index}: {0}")
|
@ParameterizedTest(name = "{index}: {0}")
|
||||||
@MethodSource("resource")
|
@MethodSource("resource")
|
||||||
void resourceCreateRelative(Resource resource) throws Exception {
|
void resourceCreateRelative(Resource resource) throws Exception {
|
||||||
Resource relative1 = resource.createRelative("ClassPathResource.class");
|
Resource relative1 = resource.createRelative("ClassPathResourceTests.class");
|
||||||
assertThat(relative1.getFilename()).isEqualTo("ClassPathResource.class");
|
assertThat(relative1.getFilename()).isEqualTo("ClassPathResourceTests.class");
|
||||||
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
|
assertThat(relative1.getURL().getFile().endsWith("ClassPathResourceTests.class")).isTrue();
|
||||||
assertThat(relative1.exists()).isTrue();
|
assertThat(relative1.exists()).isTrue();
|
||||||
assertThat(relative1.isReadable()).isTrue();
|
assertThat(relative1.isReadable()).isTrue();
|
||||||
assertThat(relative1.contentLength()).isGreaterThan(0);
|
assertThat(relative1.contentLength()).isGreaterThan(0);
|
||||||
|
|
@ -92,9 +92,9 @@ class ResourceTests {
|
||||||
@ParameterizedTest(name = "{index}: {0}")
|
@ParameterizedTest(name = "{index}: {0}")
|
||||||
@MethodSource("resource")
|
@MethodSource("resource")
|
||||||
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
|
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
|
||||||
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
|
Resource relative2 = resource.createRelative("support/PathMatchingResourcePatternResolverTests.class");
|
||||||
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
|
assertThat(relative2.getFilename()).isEqualTo("PathMatchingResourcePatternResolverTests.class");
|
||||||
assertThat(relative2.getURL().getFile()).endsWith("ResourcePatternResolver.class");
|
assertThat(relative2.getURL().getFile()).endsWith("PathMatchingResourcePatternResolverTests.class");
|
||||||
assertThat(relative2.exists()).isTrue();
|
assertThat(relative2.exists()).isTrue();
|
||||||
assertThat(relative2.isReadable()).isTrue();
|
assertThat(relative2.isReadable()).isTrue();
|
||||||
assertThat(relative2.contentLength()).isGreaterThan(0);
|
assertThat(relative2.contentLength()).isGreaterThan(0);
|
||||||
|
|
@ -104,9 +104,9 @@ class ResourceTests {
|
||||||
@ParameterizedTest(name = "{index}: {0}")
|
@ParameterizedTest(name = "{index}: {0}")
|
||||||
@MethodSource("resource")
|
@MethodSource("resource")
|
||||||
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
|
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
|
||||||
Resource relative3 = resource.createRelative("../SpringVersion.class");
|
Resource relative3 = resource.createRelative("../CollectionFactoryTests.class");
|
||||||
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
|
assertThat(relative3.getFilename()).isEqualTo("CollectionFactoryTests.class");
|
||||||
assertThat(relative3.getURL().getFile()).endsWith("SpringVersion.class");
|
assertThat(relative3.getURL().getFile()).endsWith("CollectionFactoryTests.class");
|
||||||
assertThat(relative3.exists()).isTrue();
|
assertThat(relative3.exists()).isTrue();
|
||||||
assertThat(relative3.isReadable()).isTrue();
|
assertThat(relative3.isReadable()).isTrue();
|
||||||
assertThat(relative3.contentLength()).isGreaterThan(0);
|
assertThat(relative3.contentLength()).isGreaterThan(0);
|
||||||
|
|
@ -128,12 +128,12 @@ class ResourceTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> resource() throws URISyntaxException {
|
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());
|
Path resourceClassFilePath = Paths.get(resourceClass.toURI());
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
|
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/ResourceTests.class"))),
|
||||||
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
|
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/ResourceTests.class", ResourceTests.class.getClassLoader()))),
|
||||||
arguments(named("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
|
arguments(named("ClassPathResource with Class", new ClassPathResource("ResourceTests.class", ResourceTests.class))),
|
||||||
arguments(named("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
|
arguments(named("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
|
||||||
arguments(named("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
|
arguments(named("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
|
||||||
arguments(named("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
|
arguments(named("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
|
||||||
|
|
@ -220,29 +220,29 @@ class ResourceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameResourceIsEqual() {
|
void sameResourceIsEqual() {
|
||||||
String file = getClass().getResource("Resource.class").getFile();
|
String file = getClass().getResource("ResourceTests.class").getFile();
|
||||||
Resource resource = new FileSystemResource(file);
|
Resource resource = new FileSystemResource(file);
|
||||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameResourceFromFileIsEqual() {
|
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);
|
Resource resource = new FileSystemResource(file);
|
||||||
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
assertThat(resource).isEqualTo(new FileSystemResource(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameResourceFromFilePathIsEqual() throws Exception {
|
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);
|
Resource resource = new FileSystemResource(filePath);
|
||||||
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
|
assertThat(resource).isEqualTo(new FileSystemResource(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameResourceFromDotPathIsEqual() {
|
void sameResourceFromDotPathIsEqual() {
|
||||||
Resource resource = new FileSystemResource("core/io/Resource.class");
|
Resource resource = new FileSystemResource("core/io/ResourceTests.class");
|
||||||
assertThat(new FileSystemResource("core/../core/io/./Resource.class")).isEqualTo(resource);
|
assertThat(new FileSystemResource("core/../core/io/./ResourceTests.class")).isEqualTo(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -254,7 +254,7 @@ class ResourceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void readableChannelProvidesContent() throws Exception {
|
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()) {
|
try (ReadableByteChannel channel = resource.readableChannel()) {
|
||||||
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
|
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
|
||||||
channel.read(buffer);
|
channel.read(buffer);
|
||||||
|
|
@ -293,8 +293,8 @@ class ResourceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sameResourceWithRelativePathIsEqual() throws Exception {
|
void sameResourceWithRelativePathIsEqual() throws Exception {
|
||||||
Resource resource = new UrlResource("file:core/io/Resource.class");
|
Resource resource = new UrlResource("file:core/io/ResourceTests.class");
|
||||||
assertThat(new UrlResource("file:core/../core/io/./Resource.class")).isEqualTo(resource);
|
assertThat(new UrlResource("file:core/../core/io/./ResourceTests.class")).isEqualTo(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -322,14 +322,14 @@ class ResourceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void factoryMethodsProduceEqualResources() throws Exception {
|
void factoryMethodsProduceEqualResources() throws Exception {
|
||||||
Resource resource1 = new UrlResource("file:core/io/Resource.class");
|
Resource resource1 = new UrlResource("file:core/io/ResourceTests.class");
|
||||||
Resource resource2 = UrlResource.from("file:core/io/Resource.class");
|
Resource resource2 = UrlResource.from("file:core/io/ResourceTests.class");
|
||||||
Resource resource3 = UrlResource.from(resource1.getURI());
|
Resource resource3 = UrlResource.from(resource1.getURI());
|
||||||
|
|
||||||
assertThat(resource2.getURL()).isEqualTo(resource1.getURL());
|
assertThat(resource2.getURL()).isEqualTo(resource1.getURL());
|
||||||
assertThat(resource3.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");
|
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() {
|
public String getDescription() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream() throws IOException {
|
public InputStream getInputStream() throws IOException {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
|
|
@ -459,7 +458,6 @@ class ResourceTests {
|
||||||
public InputStream getInputStream() {
|
public InputStream getInputStream() {
|
||||||
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
|
return new ByteArrayInputStream(new byte[] {'a', 'b', 'c'});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return "";
|
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" />
|
<suppress files="(^(?!.+[\\/]src[\\/]main[\\/]java[\\/].*package-info\.java))|(.*framework-docs.*)|(.*spring-(context-indexer|instrument|jcl).*)" checks="RegexpSinglelineJava" id="packageLevelNonNullFieldsAnnotation" />
|
||||||
|
|
||||||
<!-- Global: tests and test fixtures -->
|
<!-- 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|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[\\/]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[\\/]" checks="SpringJUnit5" message="should not be public"/> <suppress files="[\\/]src[\\/](test|testFixtures)[\\/](java|java21)[\\/]" checks="SpringJUnit5" message="should not be public"/>
|
||||||
|
|
||||||
<!-- JMH benchmarks -->
|
<!-- JMH benchmarks -->
|
||||||
<suppress files="[\\/]src[\\/]jmh[\\/]java[\\/]org[\\/]springframework[\\/]" checks="JavadocVariable|JavadocStyle|InnerTypeLast"/>
|
<suppress files="[\\/]src[\\/]jmh[\\/]java[\\/]org[\\/]springframework[\\/]" checks="JavadocVariable|JavadocStyle|InnerTypeLast"/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue