Skip to content

Commit

Permalink
Configuration options for virtual threads (on JDK 21)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jhoeller committed May 8, 2023
1 parent d8d7e0a commit 697d5e6
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 64 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion ci/images/get-jdk-url.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -77,12 +77,6 @@ public void execute(Runnable task) {
}
}

@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}

@Override
public Future<?> submit(Runnable task) {
FutureTask<Object> future = new FutureTask<>(task, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,6 @@ public void execute(Runnable task) {
}
}

@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}

@Override
public Future<?> submit(Runnable task) {
ExecutorService executor = getThreadPoolExecutor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,6 @@ public void execute(Runnable task) {
}
}

@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}

@Override
public Future<?> submit(Runnable task) {
ExecutorService executor = getScheduledExecutor();
Expand Down
10 changes: 10 additions & 0 deletions spring-core/spring-core.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,6 +19,7 @@
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;

Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -97,6 +100,16 @@ public SimpleAsyncTaskExecutor(ThreadFactory threadFactory) {
}


/**
* 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.
Expand Down Expand Up @@ -238,11 +251,16 @@ public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
* 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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -98,12 +98,6 @@ public void execute(Runnable task) {
}
}

@Deprecated
@Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}

@Override
public Future<?> submit(Runnable task) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ void convertsToAbsolutePathForClassRelativeAccess() {

@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();
Expand Down
Loading

0 comments on commit 697d5e6

Please sign in to comment.