From 7e7a4e5bb3b89f7d00d56ed8b00f2a3b64794d32 Mon Sep 17 00:00:00 2001 From: "James R. Perkins" Date: Wed, 10 May 2023 16:58:27 -0700 Subject: [PATCH] [RESTEASY-3314] Use a global executor service to avoid creating too many executors. https://issues.redhat.com/browse/RESTEASY-3314 Signed-off-by: James R. Perkins --- .../concurrent/ContextualExecutorService.java | 10 +- .../concurrent/ContextualExecutors.java | 55 ++--------- .../concurrent/ContextualThreadFactory.java | 53 ++++++++++ .../GlobalContextualExecutorService.java | 96 +++++++++++++++++++ ...balContextualScheduledExecutorService.java | 95 ++++++++++++++++++ .../resteasy/concurrent/SecurityActions.java | 68 +++++++++++++ 6 files changed, 324 insertions(+), 53 deletions(-) create mode 100644 resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualThreadFactory.java create mode 100644 resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualExecutorService.java create mode 100644 resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualScheduledExecutorService.java create mode 100644 resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/SecurityActions.java diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutorService.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutorService.java index 127cbdb895a..ba704a04cb5 100644 --- a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutorService.java +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutorService.java @@ -71,7 +71,7 @@ public class ContextualExecutorService implements ExecutorService { @Override public void shutdown() { if (shutdown.compareAndSet(false, true)) { - if (managed) { + if (isManaged()) { // Clear the delegate as we're done with it delegate = null; } else { @@ -87,7 +87,7 @@ public void shutdown() { @Override public List shutdownNow() { if (shutdown.compareAndSet(false, true)) { - if (managed) { + if (isManaged()) { // Clear the delegate as we're done with it delegate = null; } else { @@ -100,7 +100,7 @@ public List shutdownNow() { @Override public boolean isShutdown() { - if (managed) { + if (isManaged()) { return shutdown.get(); } return getDelegate().isShutdown(); @@ -108,7 +108,7 @@ public boolean isShutdown() { @Override public boolean isTerminated() { - if (managed) { + if (isManaged()) { return false; } return getDelegate().isTerminated(); @@ -116,7 +116,7 @@ public boolean isTerminated() { @Override public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { - if (managed) { + if (isManaged()) { return false; } return getDelegate().awaitTermination(timeout, unit); diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutors.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutors.java index 0b7e21a0706..ab788ae66df 100644 --- a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutors.java +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualExecutors.java @@ -19,8 +19,6 @@ package org.jboss.resteasy.concurrent; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -31,9 +29,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; import javax.naming.InitialContext; @@ -43,7 +39,6 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.concurrent.ThreadContext; import org.jboss.resteasy.spi.concurrent.ThreadContexts; -import org.jboss.resteasy.spi.config.ConfigurationFactory; /** * A utility to create and/or wrap {@linkplain ExecutorService executors} in a contextual executor. @@ -87,12 +82,10 @@ public static Executor executor() { */ public static ContextualExecutorService threadPool() { ExecutorService delegate = lookup(EXECUTOR_SERVICE_JNDI); - boolean managed = true; if (delegate == null) { - delegate = Executors.newCachedThreadPool(new ContextualThreadFactory("contextual-pool")); - managed = false; + delegate = GlobalContextualExecutorService.INSTANCE; } - return wrap(delegate, managed); + return wrap(delegate, true); } /** @@ -115,8 +108,11 @@ public static ContextualExecutorService threadPool() { * @return a new contextual executor */ public static ContextualScheduledExecutorService scheduledThreadPool() { - final int poolSize = getConfigValue("resteasy.async.timeout.scheduler.min.pool.size", Integer.class, () -> 1); - return scheduledThreadPool(poolSize, new ContextualThreadFactory("contextual-scheduled-pool")); + ScheduledExecutorService delegate = lookup(SCHEDULED_EXECUTOR_SERVICE_JNDI); + if (delegate == null) { + delegate = GlobalContextualScheduledExecutorService.INSTANCE; + } + return wrap(delegate, true); } /** @@ -379,41 +375,4 @@ private static T lookup(final String jndiName) { return null; } - @SuppressWarnings("SameParameterValue") - private static T getConfigValue(final String name, final Class type, final Supplier dft) { - if (System.getSecurityManager() == null) { - return ConfigurationFactory.getInstance().getConfiguration() - .getOptionalValue(name, type) - .orElseGet(dft); - } - return AccessController.doPrivileged((PrivilegedAction) () -> ConfigurationFactory.getInstance().getConfiguration() - .getOptionalValue(name, type) - .orElseGet(dft)); - } - - private static class ContextualThreadFactory implements ThreadFactory { - private static final AtomicInteger POOL_COUNTER = new AtomicInteger(0); - private final AtomicInteger threadCounter = new AtomicInteger(0); - private final String prefix; - - private ContextualThreadFactory(final String prefix) { - this.prefix = prefix; - } - - @Override - public Thread newThread(final Runnable r) { - final Thread thread = new Thread(r, String.format("%s-%d-thread-%d", prefix, POOL_COUNTER.incrementAndGet(), - threadCounter.incrementAndGet())); - if (System.getSecurityManager() == null) { - thread.setDaemon(true); - thread.setPriority(Thread.NORM_PRIORITY); - return thread; - } - return AccessController.doPrivileged((PrivilegedAction) () -> { - thread.setDaemon(true); - thread.setPriority(Thread.NORM_PRIORITY); - return thread; - }); - } - } } diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualThreadFactory.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualThreadFactory.java new file mode 100644 index 00000000000..93710d328bf --- /dev/null +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/ContextualThreadFactory.java @@ -0,0 +1,53 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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 + * + * http://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.jboss.resteasy.concurrent; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author James R. Perkins + */ +class ContextualThreadFactory implements ThreadFactory { + private static final AtomicInteger POOL_COUNTER = new AtomicInteger(0); + private final AtomicInteger threadCounter = new AtomicInteger(0); + private final String prefix; + + ContextualThreadFactory(final String prefix) { + this.prefix = String.format("%s-%d-thread", prefix, POOL_COUNTER.incrementAndGet()); + } + + @Override + public Thread newThread(final Runnable r) { + final Thread thread = new Thread(r, String.format("%s-%d", prefix, threadCounter.incrementAndGet())); + if (System.getSecurityManager() == null) { + thread.setDaemon(true); + thread.setPriority(Thread.NORM_PRIORITY); + return thread; + } + return AccessController.doPrivileged((PrivilegedAction) () -> { + thread.setDaemon(true); + thread.setPriority(Thread.NORM_PRIORITY); + return thread; + }); + } +} diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualExecutorService.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualExecutorService.java new file mode 100644 index 00000000000..9909c2a8331 --- /dev/null +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualExecutorService.java @@ -0,0 +1,96 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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 + * + * http://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.jboss.resteasy.concurrent; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author James R. Perkins + */ +class GlobalContextualExecutorService extends ContextualExecutorService implements AutoCloseable { + static final GlobalContextualExecutorService INSTANCE = new GlobalContextualExecutorService(); + private final Thread shutdownHook; + + private volatile ExecutorService delegate; + + private GlobalContextualExecutorService() { + super(null, true); + shutdownHook = new Thread("resteasy-shutdown") { + @Override + public void run() { + synchronized (GlobalContextualExecutorService.this) { + if (delegate != null) { + delegate.shutdown(); + delegate = null; + } + } + } + }; + } + + @Override + public void shutdown() { + // Do nothing as we will shut it down later + } + + @Override + public List shutdownNow() { + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + ExecutorService getDelegate() { + if (delegate == null) { + synchronized (this) { + if (delegate == null) { + final int poolSize = SecurityActions.getCoreThreads("dev.resteasy.concurrent.core.pool.size"); + delegate = Executors + .newFixedThreadPool(poolSize, new ContextualThreadFactory("contextual-pool")); + SecurityActions.registerShutdownHook(shutdownHook); + } + } + } + return delegate; + } + + @Override + public void close() { + synchronized (this) { + SecurityActions.removeShutdownHook(shutdownHook); + if (delegate != null) { + delegate.shutdown(); + delegate = null; + } + } + } +} diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualScheduledExecutorService.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualScheduledExecutorService.java new file mode 100644 index 00000000000..c4baae3aa48 --- /dev/null +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/GlobalContextualScheduledExecutorService.java @@ -0,0 +1,95 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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 + * + * http://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.jboss.resteasy.concurrent; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * @author James R. Perkins + */ +class GlobalContextualScheduledExecutorService extends ContextualScheduledExecutorService implements AutoCloseable { + static final GlobalContextualScheduledExecutorService INSTANCE = new GlobalContextualScheduledExecutorService(); + private final Thread shutdownHook; + private volatile ScheduledExecutorService delegate; + + private GlobalContextualScheduledExecutorService() { + super(null, true); + shutdownHook = new Thread("resteasy-shutdown") { + @Override + public void run() { + synchronized (GlobalContextualScheduledExecutorService.this) { + if (delegate != null) { + delegate.shutdown(); + delegate = null; + } + } + } + }; + } + + @Override + public void shutdown() { + // Do nothing as we will shut it down later + } + + @Override + public List shutdownNow() { + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + ScheduledExecutorService getDelegate() { + if (delegate == null) { + synchronized (this) { + if (delegate == null) { + final int poolSize = SecurityActions.getCoreThreads("resteasy.async.timeout.scheduler.min.pool.size"); + delegate = Executors.newScheduledThreadPool(poolSize, + new ContextualThreadFactory("contextual-scheduled-pool")); + SecurityActions.registerShutdownHook(shutdownHook); + } + } + } + return delegate; + } + + @Override + public void close() { + synchronized (this) { + SecurityActions.removeShutdownHook(shutdownHook); + if (delegate != null) { + delegate.shutdown(); + delegate = null; + } + } + } +} diff --git a/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/SecurityActions.java b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/SecurityActions.java new file mode 100644 index 00000000000..078f05ae950 --- /dev/null +++ b/resteasy-core-spi/src/main/java/org/jboss/resteasy/concurrent/SecurityActions.java @@ -0,0 +1,68 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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 + * + * http://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.jboss.resteasy.concurrent; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Never to be used outside of this package. + * + * @author James R. Perkins + */ +class SecurityActions { + + static int getCoreThreads(final String name) { + final var value = getSystemProperty(name); + if (value == null) { + return Math.max(5, Runtime.getRuntime().availableProcessors()); + } + return Integer.parseInt(value); + } + + static void registerShutdownHook(final Thread hook) { + if (System.getSecurityManager() == null) { + Runtime.getRuntime().addShutdownHook(hook); + } else { + AccessController.doPrivileged((PrivilegedAction) () -> { + Runtime.getRuntime().addShutdownHook(hook); + return null; + }); + } + } + + static void removeShutdownHook(final Thread hook) { + if (System.getSecurityManager() == null) { + Runtime.getRuntime().removeShutdownHook(hook); + } else { + AccessController.doPrivileged((PrivilegedAction) () -> { + Runtime.getRuntime().removeShutdownHook(hook); + return null; + }); + } + } + + private static String getSystemProperty(final String name) { + if (System.getSecurityManager() == null) { + return System.getProperty(name); + } + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(name)); + } +}