/* * 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.common.util.concurrent; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.test.ESTestCase; import org.hamcrest.Matcher; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.lessThan; /** * Tests for EsExecutors and its components like EsAbortPolicy. */ public class EsExecutorsTests extends ESTestCase { private TimeUnit randomTimeUnit() { return TimeUnit.values()[between(0, TimeUnit.values().length - 1)]; } public void testFixedForcedExecution() throws Exception { EsThreadPoolExecutor executor = EsExecutors.newFixed(getTestName(), 1, 1, EsExecutors.daemonThreadFactory("test")); final CountDownLatch wait = new CountDownLatch(1); final CountDownLatch exec1Wait = new CountDownLatch(1); final AtomicBoolean executed1 = new AtomicBoolean(); executor.execute(new Runnable() { @Override public void run() { try { wait.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } executed1.set(true); exec1Wait.countDown(); } }); final CountDownLatch exec2Wait = new CountDownLatch(1); final AtomicBoolean executed2 = new AtomicBoolean(); executor.execute(new Runnable() { @Override public void run() { executed2.set(true); exec2Wait.countDown(); } }); final AtomicBoolean executed3 = new AtomicBoolean(); final CountDownLatch exec3Wait = new CountDownLatch(1); executor.execute(new AbstractRunnable() { @Override protected void doRun() { executed3.set(true); exec3Wait.countDown(); } @Override public boolean isForceExecution() { return true; } @Override public void onFailure(Throwable t) { throw new AssertionError(t); } }); wait.countDown(); exec1Wait.await(); exec2Wait.await(); exec3Wait.await(); assertThat(executed1.get(), equalTo(true)); assertThat(executed2.get(), equalTo(true)); assertThat(executed3.get(), equalTo(true)); executor.shutdownNow(); } public void testFixedRejected() throws Exception { EsThreadPoolExecutor executor = EsExecutors.newFixed(getTestName(), 1, 1, EsExecutors.daemonThreadFactory("test")); final CountDownLatch wait = new CountDownLatch(1); final CountDownLatch exec1Wait = new CountDownLatch(1); final AtomicBoolean executed1 = new AtomicBoolean(); executor.execute(new Runnable() { @Override public void run() { try { wait.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } executed1.set(true); exec1Wait.countDown(); } }); final CountDownLatch exec2Wait = new CountDownLatch(1); final AtomicBoolean executed2 = new AtomicBoolean(); executor.execute(new Runnable() { @Override public void run() { executed2.set(true); exec2Wait.countDown(); } }); final AtomicBoolean executed3 = new AtomicBoolean(); try { executor.execute(new Runnable() { @Override public void run() { executed3.set(true); } }); fail("should be rejected..."); } catch (EsRejectedExecutionException e) { // all is well } wait.countDown(); exec1Wait.await(); exec2Wait.await(); assertThat(executed1.get(), equalTo(true)); assertThat(executed2.get(), equalTo(true)); assertThat(executed3.get(), equalTo(false)); terminate(executor); } public void testScaleUp() throws Exception { final int min = between(1, 3); final int max = between(min + 1, 6); final ThreadBarrier barrier = new ThreadBarrier(max + 1); ThreadPoolExecutor pool = EsExecutors.newScaling(getTestName(), min, max, between(1, 100), randomTimeUnit(), EsExecutors.daemonThreadFactory("test")); assertThat("Min property", pool.getCorePoolSize(), equalTo(min)); assertThat("Max property", pool.getMaximumPoolSize(), equalTo(max)); for (int i = 0; i < max; ++i) { final CountDownLatch latch = new CountDownLatch(1); pool.execute(new Runnable() { @Override public void run() { latch.countDown(); try { barrier.await(); barrier.await(); } catch (Throwable e) { barrier.reset(e); } } }); //wait until thread executes this task //otherwise, a task might be queued latch.await(); } barrier.await(); assertThat("wrong pool size", pool.getPoolSize(), equalTo(max)); assertThat("wrong active size", pool.getActiveCount(), equalTo(max)); barrier.await(); terminate(pool); } public void testScaleDown() throws Exception { final int min = between(1, 3); final int max = between(min + 1, 6); final ThreadBarrier barrier = new ThreadBarrier(max + 1); final ThreadPoolExecutor pool = EsExecutors.newScaling(getTestName(), min, max, between(1, 100), TimeUnit.MILLISECONDS, EsExecutors.daemonThreadFactory("test")); assertThat("Min property", pool.getCorePoolSize(), equalTo(min)); assertThat("Max property", pool.getMaximumPoolSize(), equalTo(max)); for (int i = 0; i < max; ++i) { final CountDownLatch latch = new CountDownLatch(1); pool.execute(new Runnable() { @Override public void run() { latch.countDown(); try { barrier.await(); barrier.await(); } catch (Throwable e) { barrier.reset(e); } } }); //wait until thread executes this task //otherwise, a task might be queued latch.await(); } barrier.await(); assertThat("wrong pool size", pool.getPoolSize(), equalTo(max)); assertThat("wrong active size", pool.getActiveCount(), equalTo(max)); barrier.await(); assertBusy(new Runnable() { @Override public void run() { assertThat("wrong active count", pool.getActiveCount(), equalTo(0)); assertThat("idle threads didn't shrink below max. (" + pool.getPoolSize() + ")", pool.getPoolSize(), lessThan(max)); } }); terminate(pool); } public void testRejectionMessageAndShuttingDownFlag() throws InterruptedException { int pool = between(1, 10); int queue = between(0, 100); int actions = queue + pool; final CountDownLatch latch = new CountDownLatch(1); EsThreadPoolExecutor executor = EsExecutors.newFixed(getTestName(), pool, queue, EsExecutors.daemonThreadFactory("dummy")); try { for (int i = 0; i < actions; i++) { executor.execute(new Runnable() { @Override public void run() { try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); } try { executor.execute(new Runnable() { @Override public void run() { // Doesn't matter is going to be rejected } @Override public String toString() { return "dummy runnable"; } }); fail("Didn't get a rejection when we expected one."); } catch (EsRejectedExecutionException e) { assertFalse("Thread pool registering as terminated when it isn't", e.isExecutorShutdown()); String message = ExceptionsHelper.detailedMessage(e); assertThat(message, containsString("of dummy runnable")); assertThat(message, containsString("on EsThreadPoolExecutor[testRejectionMessage")); assertThat(message, containsString("queue capacity = " + queue)); assertThat(message, containsString("[Running")); /* * While you'd expect all threads in the pool to be active when the queue gets long enough to cause rejections this isn't * always the case. Sometimes you'll see "active threads = <pool - 1>", presumably because one of those threads has finished * its current task but has yet to pick up another task. You too can reproduce this by adding the @Repeat annotation to this * test with something like 10000 iterations. I suspect you could see "active threads = <any natural number <= to pool>". So * that is what we assert. */ @SuppressWarnings("unchecked") Matcher<String>[] activeThreads = new Matcher[pool + 1]; for (int p = 0; p <= pool; p++) { activeThreads[p] = containsString("active threads = " + p); } assertThat(message, anyOf(activeThreads)); assertThat(message, containsString("queued tasks = " + queue)); assertThat(message, containsString("completed tasks = 0")); } } finally { latch.countDown(); terminate(executor); } try { executor.execute(new Runnable() { @Override public void run() { // Doesn't matter is going to be rejected } @Override public String toString() { return "dummy runnable"; } }); fail("Didn't get a rejection when we expected one."); } catch (EsRejectedExecutionException e) { assertTrue("Thread pool not registering as terminated when it is", e.isExecutorShutdown()); String message = ExceptionsHelper.detailedMessage(e); assertThat(message, containsString("of dummy runnable")); assertThat(message, containsString("on EsThreadPoolExecutor[" + getTestName())); assertThat(message, containsString("queue capacity = " + queue)); assertThat(message, containsString("[Terminated")); assertThat(message, containsString("active threads = 0")); assertThat(message, containsString("queued tasks = 0")); assertThat(message, containsString("completed tasks = " + actions)); } } }