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 1a548dcb26..c43051242a 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 @@ class NettyConnector implements Connector { NettyConnector(Client client) { - final Map properties = client.getConfiguration().getProperties(); + final Configuration configuration = client.getConfiguration(); + final Map 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 4164e3a40e..a6d823c26a 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 @@ public static HttpServer createHttpServer(final URI uri, : 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 1eb0bde634..0fefd14b55 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 @@ private static HttpServer create(URI u, Class c, Servlet serv } } + 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 b0f7663aa2..992795470e 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 @@ public static Server createServer(final URI uri, } 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 @@ public static Server createServer(final URI uri, // // 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 df26b22587..00b18ef284 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 @@ class ClientExecutorProvidersConfigurator extends AbstractExecutorProvidersConfi @Override public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { - Map runtimeProperties = bootstrapBag.getConfiguration().getProperties(); + final Configuration configuration = bootstrapBag.getConfiguration(); + Map runtimeProperties = configuration.getProperties(); ExecutorServiceProvider defaultAsyncExecutorProvider; ScheduledExecutorServiceProvider defaultScheduledExecutorProvider; @@ -94,12 +96,12 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { .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 a875febd0b..e25e30304e 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 @@ class DefaultClientAsyncExecutorProvider extends ThreadPoolExecutorProvider { * 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() { @Override diff --git a/core-common/pom.xml b/core-common/pom.xml index a2e68f532f..11d3977f5a 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -782,7 +782,7 @@ - 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 bc76ba6f3a..0c5e824e66 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 @@ -320,6 +320,30 @@ public final class CommonProperties { */ public static final String PARAM_CONVERTERS_THROW_IAE = "jersey.config.paramconverters.throw.iae"; + /** + *

+ * Defines the {@link java.util.concurrent.ThreadFactory} to be used by internal default Executor Services. + *

+ *

+ * The default is {@link java.util.concurrent.Executors#defaultThreadFactory()} on platform threads and + * {@code Thread.ofVirtual().factory()} on virtual threads. + *

+ * @since 2.44 + */ + public static String THREAD_FACTORY = "jersey.config.threads.factory"; + + /** + *

+ * Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number + * of threads by {@code FixedThreadPool}. + *

+ *

+ * The default is {@code false} for this version of Jersey, and {@code true} for Jersey 3.1+. + *

+ * @since 2.44 + */ + public static String USE_VIRTUAL_THREADS = "jersey.config.threads.use.virtual"; + /** * Prevent instantiation. */ 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 0000000000..8d3beeacb8 --- /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 0000000000..906574623c --- /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 0000000000..c56f91ba04 --- /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 27cf44959c..ff1b757d56 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. *

@@ -69,9 +72,7 @@ public abstract class AbstractThreadPoolProvider i private final String name; private final AtomicBoolean closed = new AtomicBoolean(false); - private final LazyValue lazyExecutorServiceProvider = - Values.lazy((Value) () -> createExecutor(getCorePoolSize(), createThreadFactory(), getRejectedExecutionHandler())); - + private final LazyValue lazyExecutorServiceProvider; /** * Inheritance constructor. * @@ -79,7 +80,20 @@ public abstract class AbstractThreadPoolProvider i * 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) () -> + createExecutor(getCorePoolSize(), createThreadFactory(configuration), getRejectedExecutionHandler())); } /** @@ -208,9 +222,10 @@ protected ThreadFactory getBackingThreadFactory() { 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 486226c11e..c516aecf60 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 @@ public ScheduledThreadPoolExecutorProvider(final String name) { 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 dbcec5581f..591983f4c3 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 @@ public ThreadPoolExecutorProvider(final String name) { 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 90cafba6a2..867a65be87 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 @@ private VirtualThreadSupport() { 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 74f58ba9da..0e7d6959ea 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 @@ private VirtualThreadSupport() { 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 77974f1946..ecbccfdca5 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 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { 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 schedulerBinding = Bindings .service(defaultScheduledExecutorProvider) .to(ScheduledExecutorServiceProvider.class) @@ -67,8 +68,8 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { @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 6f5036b779..8d40af6c64 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -202,6 +202,39 @@ + + &jersey.common.CommonProperties.THREAD_FACTORY;(Jersey 2.44 or later) + + + jersey.config.threads.factory + + + + Defines the java.util.concurrent.ThreadFactory to be used by internal default + ExecutorServices. + + + The default is java.util.concurrent.Executors#defaultThreadFactory() on + platform threads andThread.ofVirtual().factory() on virtual threads. + + + + + &jersey.common.CommonProperties.USE_VIRTUAL_THREADS;(Jersey 2.44 or later) + + + jersey.config.threads.use.virtual + + + + Defines whether the virtual threads should be used by Jersey on JDK 21+ when not using an exact number + of threads by FixedThreadPool. + + + The default is &lit.false; for this version of Jersey, and &lit.true; for Jersey 3.1+. + + + &jersey.logging.LoggingFeature.LOGGING_FEATURE_LOGGER_NAME; diff --git a/docs/src/main/docbook/dependencies.xml b/docs/src/main/docbook/dependencies.xml index b58ebd8016..8124dfa1ee 100644 --- a/docs/src/main/docbook/dependencies.xml +++ b/docs/src/main/docbook/dependencies.xml @@ -62,6 +62,21 @@ +

+ Virtual Threads and Thread Factories + + With JDK 21 and above, Jersey (since 2.44) has the ability to use virtual threads instead of + the CachedThreadPool in the internal ExecutorServices. + Jersey also has the ability to specify the backing ThreadFactory for the + default ExecutorServices (the default ExecutorServices + can be overridden by the &jersey.common.spi.ExecutorServiceProvider; SPI). + + + To enable virtual threads and/or specify the ThreadFactory, use + &jersey.common.CommonProperties.USE_VIRTUAL_THREADS; and/or &jersey.common.CommonProperties.THREAD_FACTORY; + properties, respectively. See also the in appendix for property details. + +
Introduction to Jersey dependencies diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent index ccbe0216ea..8cf043fc3b 100644 --- a/docs/src/main/docbook/jersey.ent +++ b/docs/src/main/docbook/jersey.ent @@ -408,6 +408,8 @@ CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT" > CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER" > CommonProperties.PARAM_CONVERTERS_THROW_IAE" > +CommonProperties.THREAD_FACTORY" > +CommonProperties.USE_VIRTUAL_THREADS" > DisposableSupplier"> InjectionManager"> InjectionResolver"> 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 a4b4e31968..43564d81c0 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 @@ class RestClientBuilderImpl implements RestClientBuilder { 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 0000000000..9c9547f805 --- /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 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)); + } +}