/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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.elasticsearch.threadpool; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.equalTo; public class ScalingThreadPoolTests extends ESThreadPoolTestCase { public void testScalingThreadPoolConfiguration() throws InterruptedException { final String threadPoolName = randomThreadPool(ThreadPool.ThreadPoolType.SCALING); final Settings.Builder builder = Settings.builder(); final int core; if (randomBoolean()) { core = randomIntBetween(0, 8); builder.put("thread_pool." + threadPoolName + ".core", core); } else { core = "generic".equals(threadPoolName) ? 4 : 1; // the defaults } final int maxBasedOnNumberOfProcessors; if (randomBoolean()) { final int processors = randomIntBetween(1, 64); maxBasedOnNumberOfProcessors = expectedSize(threadPoolName, processors); builder.put("processors", processors); } else { maxBasedOnNumberOfProcessors = expectedSize(threadPoolName, Math.min(32, Runtime.getRuntime().availableProcessors())); } final int expectedMax; if (maxBasedOnNumberOfProcessors < core || randomBoolean()) { expectedMax = randomIntBetween(Math.max(1, core), 16); builder.put("thread_pool." + threadPoolName + ".max", expectedMax); } else { expectedMax = maxBasedOnNumberOfProcessors; } final long keepAlive; if (randomBoolean()) { keepAlive = randomIntBetween(1, 300); builder.put("thread_pool." + threadPoolName + ".keep_alive", keepAlive + "s"); } else { keepAlive = "generic".equals(threadPoolName) ? 30 : 300; // the defaults } runScalingThreadPoolTest(builder.build(), (clusterSettings, threadPool) -> { final Executor executor = threadPool.executor(threadPoolName); assertThat(executor, instanceOf(EsThreadPoolExecutor.class)); final EsThreadPoolExecutor esThreadPoolExecutor = (EsThreadPoolExecutor)executor; final ThreadPool.Info info = info(threadPool, threadPoolName); assertThat(info.getName(), equalTo(threadPoolName)); assertThat(info.getThreadPoolType(), equalTo(ThreadPool.ThreadPoolType.SCALING)); assertThat(info.getKeepAlive().seconds(), equalTo(keepAlive)); assertThat(esThreadPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS), equalTo(keepAlive)); assertNull(info.getQueueSize()); assertThat(esThreadPoolExecutor.getQueue().remainingCapacity(), equalTo(Integer.MAX_VALUE)); assertThat(info.getMin(), equalTo(core)); assertThat(esThreadPoolExecutor.getCorePoolSize(), equalTo(core)); assertThat(info.getMax(), equalTo(expectedMax)); assertThat(esThreadPoolExecutor.getMaximumPoolSize(), equalTo(expectedMax)); }); } private int expectedSize(final String threadPoolName, final int numberOfProcessors) { final Map<String, Function<Integer, Integer>> sizes = new HashMap<>(); sizes.put(ThreadPool.Names.GENERIC, n -> ThreadPool.boundedBy(4 * n, 128, 512)); sizes.put(ThreadPool.Names.MANAGEMENT, n -> 5); sizes.put(ThreadPool.Names.FLUSH, ThreadPool::halfNumberOfProcessorsMaxFive); sizes.put(ThreadPool.Names.REFRESH, ThreadPool::halfNumberOfProcessorsMaxTen); sizes.put(ThreadPool.Names.WARMER, ThreadPool::halfNumberOfProcessorsMaxFive); sizes.put(ThreadPool.Names.SNAPSHOT, ThreadPool::halfNumberOfProcessorsMaxFive); sizes.put(ThreadPool.Names.FETCH_SHARD_STARTED, ThreadPool::twiceNumberOfProcessors); sizes.put(ThreadPool.Names.FETCH_SHARD_STORE, ThreadPool::twiceNumberOfProcessors); return sizes.get(threadPoolName).apply(numberOfProcessors); } public void testScalingThreadPoolIsBounded() throws InterruptedException { final String threadPoolName = randomThreadPool(ThreadPool.ThreadPoolType.SCALING); final int size = randomIntBetween(32, 512); final Settings settings = Settings.builder().put("thread_pool." + threadPoolName + ".max", size).build(); runScalingThreadPoolTest(settings, (clusterSettings, threadPool) -> { final CountDownLatch latch = new CountDownLatch(1); final int numberOfTasks = 2 * size; final CountDownLatch taskLatch = new CountDownLatch(numberOfTasks); for (int i = 0; i < numberOfTasks; i++) { threadPool.executor(threadPoolName).execute(() -> { try { latch.await(); taskLatch.countDown(); } catch (final InterruptedException e) { throw new RuntimeException(e); } }); } final ThreadPoolStats.Stats stats = stats(threadPool, threadPoolName); assertThat(stats.getQueue(), equalTo(numberOfTasks - size)); assertThat(stats.getLargest(), equalTo(size)); latch.countDown(); try { taskLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } }); } public void testScalingThreadPoolThreadsAreTerminatedAfterKeepAlive() throws InterruptedException { final String threadPoolName = randomThreadPool(ThreadPool.ThreadPoolType.SCALING); final int min = "generic".equals(threadPoolName) ? 4 : 1; final Settings settings = Settings.builder() .put("thread_pool." + threadPoolName + ".max", 128) .put("thread_pool." + threadPoolName + ".keep_alive", "1ms") .build(); runScalingThreadPoolTest(settings, ((clusterSettings, threadPool) -> { final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch taskLatch = new CountDownLatch(128); for (int i = 0; i < 128; i++) { threadPool.executor(threadPoolName).execute(() -> { try { latch.await(); taskLatch.countDown(); } catch (final InterruptedException e) { throw new RuntimeException(e); } }); } int threads = stats(threadPool, threadPoolName).getThreads(); assertEquals(128, threads); latch.countDown(); // this while loop is the core of this test; if threads // are correctly idled down by the pool, the number of // threads in the pool will drop to the min for the pool // but if threads are not correctly idled down by the pool, // this test will just timeout waiting for them to idle // down do { spinForAtLeastOneMillisecond(); } while (stats(threadPool, threadPoolName).getThreads() > min); try { taskLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } })); } public void runScalingThreadPoolTest( final Settings settings, final BiConsumer<ClusterSettings, ThreadPool> consumer) throws InterruptedException { ThreadPool threadPool = null; try { final String test = Thread.currentThread().getStackTrace()[2].getMethodName(); final Settings nodeSettings = Settings.builder().put(settings).put("node.name", test).build(); threadPool = new ThreadPool(nodeSettings); final ClusterSettings clusterSettings = new ClusterSettings(nodeSettings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); consumer.accept(clusterSettings, threadPool); } finally { terminateThreadPoolIfNeeded(threadPool); } } }