Support for Virtual Threads in Executor Services (#5648)
* Support for Virtual Threads in Executor Services
Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
index 1a548dc..c430512 100644
--- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
@@ -34,7 +34,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -88,6 +87,7 @@
import org.glassfish.jersey.client.innate.http.SSLParamConfigurator;
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter;
@@ -129,14 +129,15 @@
NettyConnector(Client client) {
- final Map<String, Object> properties = client.getConfiguration().getProperties();
+ final Configuration configuration = client.getConfiguration();
+ final Map<String, Object> properties = configuration.getProperties();
final Object threadPoolSize = properties.get(ClientProperties.ASYNC_THREADPOOL_SIZE);
if (threadPoolSize != null && threadPoolSize instanceof Integer && (Integer) threadPoolSize > 0) {
- executorService = Executors.newFixedThreadPool((Integer) threadPoolSize);
+ executorService = VirtualThreadUtil.withConfig(configuration).newFixedThreadPool((Integer) threadPoolSize);
this.group = new NioEventLoopGroup((Integer) threadPoolSize);
} else {
- executorService = Executors.newCachedThreadPool();
+ executorService = VirtualThreadUtil.withConfig(configuration).newCachedThreadPool();
this.group = new NioEventLoopGroup();
}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
index 4164e3a..a6d823c 100644
--- a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -18,10 +18,15 @@
import java.io.IOException;
import java.net.URI;
+import java.util.concurrent.ThreadFactory;
import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Configuration;
import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages;
+import org.glassfish.jersey.innate.VirtualThreadSupport;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
import org.glassfish.jersey.server.ApplicationHandler;
@@ -281,11 +286,20 @@
: uri.getPort();
final NetworkListener listener = new NetworkListener("grizzly", host, port);
+ final Configuration configuration = handler != null ? handler.getConfiguration().getConfiguration() : null;
- listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(new ThreadFactoryBuilder()
+ final LoomishExecutors executors = VirtualThreadUtil.withConfig(configuration, false);
+ final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("grizzly-http-server-%d")
.setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
- .build());
+ .setThreadFactory(executors.getThreadFactory())
+ .build();
+
+ if (executors.isVirtual()) {
+ listener.getTransport().setWorkerThreadPool(executors.newCachedThreadPool());
+ } else {
+ listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(threadFactory);
+ }
listener.setSecure(secure);
if (sslEngineConfigurator != null) {
diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
index 1eb0bde..0fefd14 100644
--- a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
+++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,6 +23,7 @@
import javax.servlet.Servlet;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.uri.UriComponent;
@@ -251,11 +252,13 @@
}
}
+ ResourceConfig configuration = new ResourceConfig();
if (initParams != null) {
registration.setInitParameters(initParams);
+ configuration.addProperties((Map) initParams);
}
- HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u);
+ HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u, configuration);
context.deploy(server);
return server;
}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
index b0f7663..9927954 100644
--- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,7 +20,10 @@
import java.util.concurrent.ThreadFactory;
import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Configuration;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.jetty.internal.LocalizationMessages;
import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
@@ -253,7 +256,8 @@
}
final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort();
- final Server server = new Server(new JettyConnectorThreadPool());
+ final Configuration configuration = handler != null ? handler.getConfiguration() : null;
+ final Server server = new Server(new JettyConnectorThreadPool(configuration));
final HttpConfiguration config = new HttpConfiguration();
if (sslContextFactory != null) {
config.setSecureScheme("https");
@@ -291,10 +295,20 @@
//
// Keeping this for backwards compatibility for the time being
private static final class JettyConnectorThreadPool extends QueuedThreadPool {
- private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
- .setNameFormat("jetty-http-server-%d")
- .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
- .build();
+ private final ThreadFactory threadFactory;
+
+ private JettyConnectorThreadPool(Configuration configuration) {
+ final LoomishExecutors executors = VirtualThreadUtil.withConfig(configuration, false);
+ if (executors.isVirtual()) {
+ super.setMaxThreads(Integer.MAX_VALUE - 1);
+ }
+
+ this.threadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("jetty-http-server-%d")
+ .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+ .setThreadFactory(executors.getThreadFactory())
+ .build();
+ }
@Override
public Thread newThread(Runnable runnable) {
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java
index df26b22..00b18ef 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientExecutorProvidersConfigurator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -32,11 +32,12 @@
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.model.internal.ComponentBag;
-import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator;
import org.glassfish.jersey.spi.ExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
+import javax.ws.rs.core.Configuration;
+
/**
* Configurator which initializes and register {@link ExecutorServiceProvider} and
* {@link ScheduledExecutorServiceProvider}.
@@ -64,7 +65,8 @@
@Override
public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
- Map<String, Object> runtimeProperties = bootstrapBag.getConfiguration().getProperties();
+ final Configuration configuration = bootstrapBag.getConfiguration();
+ Map<String, Object> runtimeProperties = configuration.getProperties();
ExecutorServiceProvider defaultAsyncExecutorProvider;
ScheduledExecutorServiceProvider defaultScheduledExecutorProvider;
@@ -94,12 +96,12 @@
.named("ClientAsyncThreadPoolSize");
injectionManager.register(asyncThreadPoolSizeBinding);
- defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize);
+ defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(asyncThreadPoolSize, configuration);
} else {
if (MANAGED_EXECUTOR_SERVICE != null) {
defaultAsyncExecutorProvider = new ClientExecutorServiceProvider(MANAGED_EXECUTOR_SERVICE);
} else {
- defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0);
+ defaultAsyncExecutorProvider = new DefaultClientAsyncExecutorProvider(0, configuration);
}
}
}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
index a875feb..e25e303 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/DefaultClientAsyncExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,6 +20,8 @@
import javax.inject.Inject;
import javax.inject.Named;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.collection.LazyValue;
@@ -46,8 +48,9 @@
* See also {@link org.glassfish.jersey.client.ClientProperties#ASYNC_THREADPOOL_SIZE}.
*/
@Inject
- public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize) {
- super("jersey-client-async-executor");
+ public DefaultClientAsyncExecutorProvider(@Named("ClientAsyncThreadPoolSize") final int poolSize,
+ @Context Configuration configuration) {
+ super("jersey-client-async-executor", configuration);
this.asyncThreadPoolSize = Values.lazy(new Value<Integer>() {
@Override
diff --git a/core-common/pom.xml b/core-common/pom.xml
index a2e68f5..11d3977 100644
--- a/core-common/pom.xml
+++ b/core-common/pom.xml
@@ -782,7 +782,7 @@
<configuration>
<target>
<mkdir dir="${java21.build.outputDirectory}" />
- <javac srcdir="${java21.sourceDirectory}" destdir="${java21.build.outputDirectory}"
+ <javac srcdir="${java21.sourceDirectory}${path.separator}${project.basedir}/src/main/java/org/glassfish/jersey/innate/virtual" destdir="${java21.build.outputDirectory}"
classpath="${project.build.outputDirectory}" includeantruntime="false" release="21"/>
</target>
</configuration>
diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
index bc76ba6..0c5e824 100644
--- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
+++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -321,6 +321,30 @@
public static final String PARAM_CONVERTERS_THROW_IAE = "jersey.config.paramconverters.throw.iae";
/**
+ * <p>
+ * Defines the {@link java.util.concurrent.ThreadFactory} to be used by internal default Executor Services.
+ * </p>
+ * <p>
+ * The default is {@link java.util.concurrent.Executors#defaultThreadFactory()} on platform threads and
+ * {@code Thread.ofVirtual().factory()} on virtual threads.
+ * </p>
+ * @since 2.44
+ */
+ public static String THREAD_FACTORY = "jersey.config.threads.factory";
+
+ /**
+ * <p>
+ * Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number
+ * of threads by {@code FixedThreadPool}.
+ * </p>
+ * <p>
+ * The default is {@code false} for this version of Jersey, and {@code true} for Jersey 3.1+.
+ * </p>
+ * @since 2.44
+ */
+ public static String USE_VIRTUAL_THREADS = "jersey.config.threads.use.virtual";
+
+ /**
* Prevent instantiation.
*/
private CommonProperties() {
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java
new file mode 100644
index 0000000..8d3beea
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/VirtualThreadUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.innate;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import javax.ws.rs.core.Configuration;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Factory class to provide JDK specific implementation of bits related to the virtual thread support.
+ */
+public final class VirtualThreadUtil {
+
+ private static final boolean USE_VIRTUAL_THREADS_BY_DEFAULT = false;
+
+ /**
+ * Do not instantiate.
+ */
+ private VirtualThreadUtil() {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a configuration property.
+ * @param config the {@link Configuration}
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors withConfig(Configuration config) {
+ return withConfig(config, USE_VIRTUAL_THREADS_BY_DEFAULT);
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a configuration property.
+ * @param config the {@link Configuration}
+ * @param useVirtualByDefault the default use if not said otherwise by property
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors withConfig(Configuration config, boolean useVirtualByDefault) {
+ ThreadFactory tfThreadFactory = null;
+ boolean useVirtualThreads = useVirtualThreads(config, useVirtualByDefault);
+
+ if (config != null) {
+ Object threadFactory = config.getProperty(CommonProperties.THREAD_FACTORY);
+ if (threadFactory != null && ThreadFactory.class.isInstance(threadFactory)) {
+ tfThreadFactory = (ThreadFactory) threadFactory;
+ }
+ }
+
+ return tfThreadFactory == null
+ ? VirtualThreadSupport.allowVirtual(useVirtualThreads)
+ : VirtualThreadSupport.allowVirtual(useVirtualThreads, tfThreadFactory);
+ }
+
+ /**
+ * Check configuration if the use of the virtual threads is expected or return the default value if not.
+ * @param config the {@link Configuration}
+ * @param useByDefault the default expectation
+ * @return the expected
+ */
+ private static boolean useVirtualThreads(Configuration config, boolean useByDefault) {
+ boolean bUseVirtualThreads = useByDefault;
+ if (config != null) {
+ Object useVirtualThread = config.getProperty(CommonProperties.USE_VIRTUAL_THREADS);
+ if (useVirtualThread != null && Boolean.class.isInstance(useVirtualThread)) {
+ bUseVirtualThreads = (boolean) useVirtualThread;
+ }
+ if (useVirtualThread != null && String.class.isInstance(useVirtualThread)) {
+ bUseVirtualThreads = Boolean.parseBoolean(useVirtualThread.toString());
+ }
+ }
+ return bUseVirtualThreads;
+ }
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java
new file mode 100644
index 0000000..9065746
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/LoomishExecutors.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.innate.virtual;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * {@link Executors} facade to support virtual threads.
+ */
+public interface LoomishExecutors {
+ /**
+ * Creates a thread pool that creates new threads as needed and uses virtual threads if available.
+ * @return the newly created thread pool
+ */
+ ExecutorService newCachedThreadPool();
+
+ /**
+ * Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue
+ * and uses virtual threads if available
+ * @param nThreads – the number of threads in the pool
+ * @return the newly created thread pool
+ */
+ ExecutorService newFixedThreadPool(int nThreads);
+
+ /**
+ * Returns thread factory used to create new threads
+ * @return thread factory used to create new threads
+ * @see Executors#defaultThreadFactory()
+ */
+ ThreadFactory getThreadFactory();
+
+ /**
+ * Return true if the virtual thread use is requested.
+ * @return whether the virtual thread use is requested.
+ */
+ boolean isVirtual();
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java
new file mode 100644
index 0000000..c56f91b
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/innate/virtual/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey innate packages. The innate packages will not be opened by JPMS outside of Jersey.
+ * Not for public use.
+ * This virtual package should contain only classes that do not have dependencies on Jersey, or the REST API to be buildable with
+ * ant for multi-release.
+ */
+package org.glassfish.jersey.innate.virtual;
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
index 27cf449..ff1b757 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/AbstractThreadPoolProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -29,6 +29,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.internal.util.ExtendedLogger;
@@ -37,6 +38,8 @@
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import javax.ws.rs.core.Configuration;
+
/**
* Abstract thread pool executor provider.
* <p>
@@ -69,9 +72,7 @@
private final String name;
private final AtomicBoolean closed = new AtomicBoolean(false);
- private final LazyValue<E> lazyExecutorServiceProvider =
- Values.lazy((Value<E>) () -> createExecutor(getCorePoolSize(), createThreadFactory(), getRejectedExecutionHandler()));
-
+ private final LazyValue<E> lazyExecutorServiceProvider;
/**
* Inheritance constructor.
*
@@ -79,7 +80,20 @@
* provided thread pool executor.
*/
protected AbstractThreadPoolProvider(final String name) {
+ this(name, null);
+ }
+
+ /**
+ * Inheritance constructor.
+ *
+ * @param name name of the provided thread pool executor. Will be used in the names of threads created & used by the
+ * provided thread pool executor.
+ * @param configuration {@link Configuration} properties.
+ */
+ protected AbstractThreadPoolProvider(final String name, Configuration configuration) {
this.name = name;
+ lazyExecutorServiceProvider = Values.lazy((Value<E>) () ->
+ createExecutor(getCorePoolSize(), createThreadFactory(configuration), getRejectedExecutionHandler()));
}
/**
@@ -208,9 +222,10 @@
return null;
}
- private ThreadFactory createThreadFactory() {
+ private ThreadFactory createThreadFactory(Configuration configuration) {
final ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder()
.setNameFormat(name + "-%d")
+ .setThreadFactory(VirtualThreadUtil.withConfig(configuration).getThreadFactory())
.setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler());
final ThreadFactory backingThreadFactory = getBackingThreadFactory();
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
index 486226c..c516aec 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/ScheduledThreadPoolExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,6 +23,7 @@
import java.util.concurrent.ThreadFactory;
import javax.annotation.PreDestroy;
+import javax.ws.rs.core.Configuration;
/**
* Default implementation of the Jersey {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider
@@ -66,6 +67,17 @@
super(name);
}
+ /**
+ * Create a new instance of the scheduled thread pool executor provider.
+ *
+ * @param name provider name. The name will be used to name the threads created & used by the
+ * provisioned scheduled thread pool executor.
+ * @@param configuration {@link Configuration} properties.
+ */
+ public ScheduledThreadPoolExecutorProvider(final String name, Configuration configuration) {
+ super(name, configuration);
+ }
+
@Override
public ScheduledExecutorService getExecutorService() {
return super.getExecutor();
diff --git a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
index dbcec55..591983f 100644
--- a/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/spi/ThreadPoolExecutorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -26,6 +26,7 @@
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
+import javax.ws.rs.core.Configuration;
/**
* Default implementation of the Jersey {@link org.glassfish.jersey.spi.ExecutorServiceProvider executor service provider SPI}.
@@ -61,6 +62,17 @@
super(name);
}
+ /**
+ * Create a new instance of the thread pool executor provider.
+ *
+ * @param name provider name. The name will be used to name the threads created & used by the
+ * provisioned thread pool executor.
+ * @param configuration {@link Configuration} properties.
+ */
+ public ThreadPoolExecutorProvider(final String name, Configuration configuration) {
+ super(name, configuration);
+ }
+
@Override
public ExecutorService getExecutorService() {
return super.getExecutor();
diff --git a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
index 90cafba..867a65b 100644
--- a/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
+++ b/core-common/src/main/java20-/org/glassfish/jersey/innate/VirtualThreadSupport.java
@@ -16,11 +16,19 @@
package org.glassfish.jersey.innate;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
/**
* Utility class for the virtual thread support.
*/
public final class VirtualThreadSupport {
+ private static final LoomishExecutors NON_VIRTUAL = new NonLoomishExecutors(Executors.defaultThreadFactory());
+
/**
* Do not instantiate.
*/
@@ -35,4 +43,51 @@
public static boolean isVirtualThread() {
return false;
}
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow) {
+ return NON_VIRTUAL;
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @param threadFactory the thread factory to be used by a the {@link ExecutorService}.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) {
+ return new NonLoomishExecutors(threadFactory);
+ }
+
+ private static final class NonLoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private NonLoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newCachedThreadPool();
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return Executors.newFixedThreadPool(nThreads);
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return false;
+ }
+ }
}
diff --git a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
index 74f58ba..0e7d695 100644
--- a/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
+++ b/core-common/src/main/java21/org/glassfish/jersey/innate/VirtualThreadSupport.java
@@ -16,11 +16,20 @@
package org.glassfish.jersey.innate;
+import org.glassfish.jersey.innate.virtual.LoomishExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
/**
* Utility class for the virtual thread support.
*/
public final class VirtualThreadSupport {
+ private static final LoomishExecutors VIRTUAL_THREADS = new Java21LoomishExecutors(Thread.ofVirtual().factory());
+ private static final LoomishExecutors NON_VIRTUAL_THREADS = new NonLoomishExecutors(Executors.defaultThreadFactory());
+
/**
* Do not instantiate.
*/
@@ -35,4 +44,80 @@
public static boolean isVirtualThread() {
return Thread.currentThread().isVirtual();
}
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow) {
+ return allow ? VIRTUAL_THREADS : NON_VIRTUAL_THREADS;
+ }
+
+ /**
+ * Return an instance of {@link LoomishExecutors} based on a permission to use virtual threads.
+ * @param allow whether to allow virtual threads.
+ * @param threadFactory the thread factory to be used by a the {@link ExecutorService}.
+ * @return the {@link LoomishExecutors} instance.
+ */
+ public static LoomishExecutors allowVirtual(boolean allow, ThreadFactory threadFactory) {
+ return allow ? new Java21LoomishExecutors(threadFactory) : new NonLoomishExecutors(threadFactory);
+ }
+
+ private static class NonLoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private NonLoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newCachedThreadPool(getThreadFactory());
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ return Executors.newFixedThreadPool(nThreads, getThreadFactory());
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return false;
+ }
+ }
+
+ private static class Java21LoomishExecutors implements LoomishExecutors {
+ private final ThreadFactory threadFactory;
+
+ private Java21LoomishExecutors(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ @Override
+ public ExecutorService newCachedThreadPool() {
+ return Executors.newThreadPerTaskExecutor(getThreadFactory());
+ }
+
+ @Override
+ public ExecutorService newFixedThreadPool(int nThreads) {
+ ThreadFactory threadFactory = this == VIRTUAL_THREADS ? Executors.defaultThreadFactory() : getThreadFactory();
+ return Executors.newFixedThreadPool(nThreads, threadFactory);
+ }
+
+ @Override
+ public ThreadFactory getThreadFactory() {
+ return threadFactory;
+ }
+
+ @Override
+ public boolean isVirtual() {
+ return true;
+ }
+ }
}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
index 77974f1..ecbccfd 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerExecutorProvidersConfigurator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -21,13 +21,14 @@
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.model.internal.ComponentBag;
-import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator;
import org.glassfish.jersey.spi.ExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
import org.glassfish.jersey.spi.ScheduledThreadPoolExecutorProvider;
import org.glassfish.jersey.spi.ThreadPoolExecutorProvider;
+import javax.ws.rs.core.Configuration;
+
/**
* Configurator which initializes and register {@link org.glassfish.jersey.spi.ExecutorServiceProvider} and
* {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider}.
@@ -43,7 +44,7 @@
ComponentBag componentBag = runtimeConfig.getComponentBag();
// TODO: Do we need to register DEFAULT Executor and ScheduledExecutor to InjectionManager?
- ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider();
+ ScheduledExecutorServiceProvider defaultScheduledExecutorProvider = new DefaultBackgroundSchedulerProvider(runtimeConfig);
InstanceBinding<ScheduledExecutorServiceProvider> schedulerBinding = Bindings
.service(defaultScheduledExecutorProvider)
.to(ScheduledExecutorServiceProvider.class)
@@ -67,8 +68,8 @@
@BackgroundScheduler
private static class DefaultBackgroundSchedulerProvider extends ScheduledThreadPoolExecutorProvider {
- public DefaultBackgroundSchedulerProvider() {
- super("jersey-background-task-scheduler");
+ public DefaultBackgroundSchedulerProvider(Configuration configuration) {
+ super("jersey-background-task-scheduler", configuration);
}
@Override
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 6f5036b..8d40af6 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -203,6 +203,39 @@
</entry>
</row>
<row>
+ <entry>&jersey.common.CommonProperties.THREAD_FACTORY;(Jersey 2.44 or later)
+ </entry>
+ <entry>
+ <literal>jersey.config.threads.factory</literal>
+ </entry>
+ <entry>
+ <para>
+ Defines the <literal>java.util.concurrent.ThreadFactory</literal> to be used by internal default
+ <literal>ExecutorServices</literal>.
+ </para>
+ <para>
+ The default is <literal>java.util.concurrent.Executors#defaultThreadFactory()</literal> on
+ platform threads and<literal>Thread.ofVirtual().factory()</literal> on virtual threads.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>&jersey.common.CommonProperties.USE_VIRTUAL_THREADS;(Jersey 2.44 or later)
+ </entry>
+ <entry>
+ <literal>jersey.config.threads.use.virtual</literal>
+ </entry>
+ <entry>
+ <para>
+ Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number
+ of threads by <literal>FixedThreadPool</literal>.
+ </para>
+ <para>
+ The default is &lit.false; for this version of Jersey, and &lit.true; for Jersey 3.1+.
+ </para>
+ </entry>
+ </row>
+ <row>
<entry>&jersey.logging.LoggingFeature.LOGGING_FEATURE_LOGGER_NAME;
</entry>
<entry>
diff --git a/docs/src/main/docbook/dependencies.xml b/docs/src/main/docbook/dependencies.xml
index b58ebd8..8124dfa 100644
--- a/docs/src/main/docbook/dependencies.xml
+++ b/docs/src/main/docbook/dependencies.xml
@@ -62,6 +62,21 @@
</listitem>
</itemizedlist>
</para>
+ <section>
+ <title>Virtual Threads and Thread Factories</title>
+ <para>
+ With JDK 21 and above, Jersey (since 2.44) has the ability to use virtual threads instead of
+ the <literal>CachedThreadPool</literal> in the internal <literal>ExecutorServices</literal>.
+ Jersey also has the ability to specify the backing <literal>ThreadFactory</literal> for the
+ default <literal>ExecutorServices</literal> (the default <literal>ExecutorServices</literal>
+ can be overridden by the &jersey.common.spi.ExecutorServiceProvider; SPI).
+ </para>
+ <para>
+ To enable virtual threads and/or specify the <literal>ThreadFactory</literal>, use
+ &jersey.common.CommonProperties.USE_VIRTUAL_THREADS; and/or &jersey.common.CommonProperties.THREAD_FACTORY;
+ properties, respectively. See also the <xref linkend="appendix-properties-common"/> in appendix for property details.
+ </para>
+ </section>
</section>
<section>
<title>Introduction to Jersey dependencies</title>
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index ccbe021..8cf043f 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -408,6 +408,8 @@
<!ENTITY jersey.common.CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_JACKSON_DISABLED_MODULES'>CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT</link>" >
<!ENTITY jersey.common.CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_JACKSON_DISABLED_MODULES'>CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER</link>" >
<!ENTITY jersey.common.CommonProperties.PARAM_CONVERTERS_THROW_IAE "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#PARAM_CONVERTERS_THROW_IAE'>CommonProperties.PARAM_CONVERTERS_THROW_IAE</link>" >
+<!ENTITY jersey.common.CommonProperties.THREAD_FACTORY "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#THREAD_FACTORY'>CommonProperties.THREAD_FACTORY</link>" >
+<!ENTITY jersey.common.CommonProperties.USE_VIRTUAL_THREADS "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#USE_VIRTUAL_THREADS'>CommonProperties.USE_VIRTUAL_THREADS</link>" >
<!ENTITY jersey.common.internal.inject.DisposableSupplier "<link xlink:href='&jersey.javadoc.uri.prefix;/internal/inject/DisposableSupplier.html'>DisposableSupplier</link>">
<!ENTITY jersey.common.internal.inject.InjectionManager "<link xlink:href='&jersey.javadoc.uri.prefix;/internal/inject/InjectionManager.html'>InjectionManager</link>">
<!ENTITY jersey.common.internal.inject.InjectionResolver "<link xlink:href='&jersey.javadoc.uri.prefix;/internal/inject/InjectionResolver.html'>InjectionResolver</link>">
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
index a4b4e31..43564d8 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Payara Foundation and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -33,7 +33,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
@@ -66,6 +65,7 @@
import org.glassfish.jersey.client.Initializable;
import org.glassfish.jersey.client.spi.ConnectorProvider;
import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
+import org.glassfish.jersey.innate.VirtualThreadUtil;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
@@ -111,7 +111,7 @@
asyncInterceptorFactories = new ArrayList<>();
config = ConfigProvider.getConfig();
configWrapper = new ConfigWrapper(clientBuilder.getConfiguration());
- executorService = Executors::newCachedThreadPool;
+ executorService = () -> VirtualThreadUtil.withConfig(configWrapper).newCachedThreadPool();
}
@Override
diff --git a/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java
new file mode 100644
index 0000000..9c9547f
--- /dev/null
+++ b/tests/e2e-jdk-specifics/src/test/java/org/glassfish/jersey/tests/e2e/jdk21/ThreadFactoryUsageTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.e2e.jdk21;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.innate.VirtualThreadSupport;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadFactoryUsageTest {
+ @Test
+ public void testThreadFactory() throws ExecutionException, InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ ThreadFactory threadFactory = VirtualThreadSupport.allowVirtual(true).getThreadFactory();
+ ThreadFactory countDownThreadFactory = r -> {
+ countDownLatch.countDown();
+ return threadFactory.newThread(r);
+ };
+
+ CompletionStage<Response> r = ClientBuilder.newClient()
+ .property(CommonProperties.THREAD_FACTORY, countDownThreadFactory)
+ .property(CommonProperties.USE_VIRTUAL_THREADS, true)
+ .register((ClientRequestFilter) requestContext -> requestContext.abortWith(Response.ok().build()))
+ .target("http://localhost:58080/test").request().rx().get();
+
+ MatcherAssert.assertThat(r.toCompletableFuture().get().getStatus(), Matchers.is(200));
+ countDownLatch.await(10, TimeUnit.SECONDS);
+ MatcherAssert.assertThat(countDownLatch.getCount(), Matchers.is(0L));
+ }
+}