/* * 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.common.Priority; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class PrioritizedExecutorsTests extends ESTestCase { private final ThreadContext holder = new ThreadContext(Settings.EMPTY); public void testPriorityQueue() throws Exception { PriorityBlockingQueue<Priority> queue = new PriorityBlockingQueue<>(); List<Priority> priorities = Arrays.asList(Priority.values()); Collections.shuffle(priorities, random()); for (Priority priority : priorities) { queue.add(priority); } Priority prevPriority = null; while (!queue.isEmpty()) { if (prevPriority == null) { prevPriority = queue.poll(); } else { assertThat(queue.poll().after(prevPriority), is(true)); } } } public void testSubmitPrioritizedExecutorWithRunnables() throws Exception { ExecutorService executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, null); List<Integer> results = new ArrayList<>(8); CountDownLatch awaitingLatch = new CountDownLatch(1); CountDownLatch finishedLatch = new CountDownLatch(8); executor.submit(new AwaitingJob(awaitingLatch)); executor.submit(new Job(7, Priority.LANGUID, results, finishedLatch)); executor.submit(new Job(5, Priority.LOW, results, finishedLatch)); executor.submit(new Job(2, Priority.HIGH, results, finishedLatch)); executor.submit(new Job(6, Priority.LOW, results, finishedLatch)); // will execute after the first LOW (fifo) executor.submit(new Job(1, Priority.URGENT, results, finishedLatch)); executor.submit(new Job(4, Priority.NORMAL, results, finishedLatch)); executor.submit(new Job(3, Priority.HIGH, results, finishedLatch)); // will execute after the first HIGH (fifo) executor.submit(new Job(0, Priority.IMMEDIATE, results, finishedLatch)); awaitingLatch.countDown(); finishedLatch.await(); assertThat(results.size(), equalTo(8)); assertThat(results.get(0), equalTo(0)); assertThat(results.get(1), equalTo(1)); assertThat(results.get(2), equalTo(2)); assertThat(results.get(3), equalTo(3)); assertThat(results.get(4), equalTo(4)); assertThat(results.get(5), equalTo(5)); assertThat(results.get(6), equalTo(6)); assertThat(results.get(7), equalTo(7)); terminate(executor); } public void testExecutePrioritizedExecutorWithRunnables() throws Exception { ExecutorService executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, null); List<Integer> results = new ArrayList<>(8); CountDownLatch awaitingLatch = new CountDownLatch(1); CountDownLatch finishedLatch = new CountDownLatch(8); executor.execute(new AwaitingJob(awaitingLatch)); executor.execute(new Job(7, Priority.LANGUID, results, finishedLatch)); executor.execute(new Job(5, Priority.LOW, results, finishedLatch)); executor.execute(new Job(2, Priority.HIGH, results, finishedLatch)); executor.execute(new Job(6, Priority.LOW, results, finishedLatch)); // will execute after the first LOW (fifo) executor.execute(new Job(1, Priority.URGENT, results, finishedLatch)); executor.execute(new Job(4, Priority.NORMAL, results, finishedLatch)); executor.execute(new Job(3, Priority.HIGH, results, finishedLatch)); // will execute after the first HIGH (fifo) executor.execute(new Job(0, Priority.IMMEDIATE, results, finishedLatch)); awaitingLatch.countDown(); finishedLatch.await(); assertThat(results.size(), equalTo(8)); assertThat(results.get(0), equalTo(0)); assertThat(results.get(1), equalTo(1)); assertThat(results.get(2), equalTo(2)); assertThat(results.get(3), equalTo(3)); assertThat(results.get(4), equalTo(4)); assertThat(results.get(5), equalTo(5)); assertThat(results.get(6), equalTo(6)); assertThat(results.get(7), equalTo(7)); terminate(executor); } public void testSubmitPrioritizedExecutorWithCallables() throws Exception { ExecutorService executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, null); List<Integer> results = new ArrayList<>(8); CountDownLatch awaitingLatch = new CountDownLatch(1); CountDownLatch finishedLatch = new CountDownLatch(8); executor.submit(new AwaitingJob(awaitingLatch)); executor.submit(new CallableJob(7, Priority.LANGUID, results, finishedLatch)); executor.submit(new CallableJob(5, Priority.LOW, results, finishedLatch)); executor.submit(new CallableJob(2, Priority.HIGH, results, finishedLatch)); executor.submit(new CallableJob(6, Priority.LOW, results, finishedLatch)); // will execute after the first LOW (fifo) executor.submit(new CallableJob(1, Priority.URGENT, results, finishedLatch)); executor.submit(new CallableJob(4, Priority.NORMAL, results, finishedLatch)); executor.submit(new CallableJob(3, Priority.HIGH, results, finishedLatch)); // will execute after the first HIGH (fifo) executor.submit(new CallableJob(0, Priority.IMMEDIATE, results, finishedLatch)); awaitingLatch.countDown(); finishedLatch.await(); assertThat(results.size(), equalTo(8)); assertThat(results.get(0), equalTo(0)); assertThat(results.get(1), equalTo(1)); assertThat(results.get(2), equalTo(2)); assertThat(results.get(3), equalTo(3)); assertThat(results.get(4), equalTo(4)); assertThat(results.get(5), equalTo(5)); assertThat(results.get(6), equalTo(6)); assertThat(results.get(7), equalTo(7)); terminate(executor); } public void testSubmitPrioritizedExecutorWithMixed() throws Exception { ExecutorService executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, null); List<Integer> results = new ArrayList<>(8); CountDownLatch awaitingLatch = new CountDownLatch(1); CountDownLatch finishedLatch = new CountDownLatch(8); executor.submit(new AwaitingJob(awaitingLatch)); executor.submit(new CallableJob(7, Priority.LANGUID, results, finishedLatch)); executor.submit(new Job(5, Priority.LOW, results, finishedLatch)); executor.submit(new CallableJob(2, Priority.HIGH, results, finishedLatch)); executor.submit(new Job(6, Priority.LOW, results, finishedLatch)); // will execute after the first LOW (fifo) executor.submit(new CallableJob(1, Priority.URGENT, results, finishedLatch)); executor.submit(new Job(4, Priority.NORMAL, results, finishedLatch)); executor.submit(new CallableJob(3, Priority.HIGH, results, finishedLatch)); // will execute after the first HIGH (fifo) executor.submit(new Job(0, Priority.IMMEDIATE, results, finishedLatch)); awaitingLatch.countDown(); finishedLatch.await(); assertThat(results.size(), equalTo(8)); assertThat(results.get(0), equalTo(0)); assertThat(results.get(1), equalTo(1)); assertThat(results.get(2), equalTo(2)); assertThat(results.get(3), equalTo(3)); assertThat(results.get(4), equalTo(4)); assertThat(results.get(5), equalTo(5)); assertThat(results.get(6), equalTo(6)); assertThat(results.get(7), equalTo(7)); terminate(executor); } public void testTimeout() throws Exception { ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(EsExecutors.daemonThreadFactory(getTestName())); PrioritizedEsThreadPoolExecutor executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, timer); final CountDownLatch invoked = new CountDownLatch(1); final CountDownLatch block = new CountDownLatch(1); executor.execute(new Runnable() { @Override public void run() { try { invoked.countDown(); block.await(); } catch (InterruptedException e) { fail(); } } @Override public String toString() { return "the blocking"; } }); invoked.await(); PrioritizedEsThreadPoolExecutor.Pending[] pending = executor.getPending(); assertThat(pending.length, equalTo(1)); assertThat(pending[0].task.toString(), equalTo("the blocking")); assertThat(pending[0].executing, equalTo(true)); final AtomicBoolean executeCalled = new AtomicBoolean(); final CountDownLatch timedOut = new CountDownLatch(1); executor.execute(new Runnable() { @Override public void run() { executeCalled.set(true); } @Override public String toString() { return "the waiting"; } }, TimeValue.timeValueMillis(100) /* enough timeout to catch them in the pending list... */, new Runnable() { @Override public void run() { timedOut.countDown(); } } ); pending = executor.getPending(); assertThat(pending.length, equalTo(2)); assertThat(pending[0].task.toString(), equalTo("the blocking")); assertThat(pending[0].executing, equalTo(true)); assertThat(pending[1].task.toString(), equalTo("the waiting")); assertThat(pending[1].executing, equalTo(false)); assertThat(timedOut.await(2, TimeUnit.SECONDS), equalTo(true)); block.countDown(); Thread.sleep(100); // sleep a bit to double check that execute on the timed out update task is not called... assertThat(executeCalled.get(), equalTo(false)); assertTrue(terminate(timer, executor)); } public void testTimeoutCleanup() throws Exception { ThreadPool threadPool = new TestThreadPool("test"); final ScheduledThreadPoolExecutor timer = (ScheduledThreadPoolExecutor) threadPool.scheduler(); final AtomicBoolean timeoutCalled = new AtomicBoolean(); PrioritizedEsThreadPoolExecutor executor = EsExecutors.newSinglePrioritizing(getTestName(), EsExecutors.daemonThreadFactory(getTestName()), holder, timer); final CountDownLatch invoked = new CountDownLatch(1); executor.execute(new Runnable() { @Override public void run() { invoked.countDown(); } }, TimeValue.timeValueHours(1), new Runnable() { @Override public void run() { // We should never get here timeoutCalled.set(true); } } ); invoked.await(); // the timeout handler is added post execution (and quickly cancelled). We have allow for this // and use assert busy assertBusy(new Runnable() { @Override public void run() { assertThat(timer.getQueue().size(), equalTo(0)); } }, 5, TimeUnit.SECONDS); assertThat(timeoutCalled.get(), equalTo(false)); assertTrue(terminate(executor)); assertTrue(terminate(threadPool)); } static class AwaitingJob extends PrioritizedRunnable { private final CountDownLatch latch; private AwaitingJob(CountDownLatch latch) { super(Priority.URGENT); this.latch = latch; } @Override public void run() { try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } static class Job extends PrioritizedRunnable { private final int result; private final List<Integer> results; private final CountDownLatch latch; Job(int result, Priority priority, List<Integer> results, CountDownLatch latch) { super(priority); this.result = result; this.results = results; this.latch = latch; } @Override public void run() { results.add(result); latch.countDown(); } } static class CallableJob extends PrioritizedCallable<Integer> { private final int result; private final List<Integer> results; private final CountDownLatch latch; CallableJob(int result, Priority priority, List<Integer> results, CountDownLatch latch) { super(priority); this.result = result; this.results = results; this.latch = latch; } @Override public Integer call() throws Exception { results.add(result); latch.countDown(); return result; } } }