All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */
package org.elasticsearch.common.util.concurrent;

import org.elasticsearch.common.Priority;
import org.elasticsearch.core.TimeValue;

import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A prioritizing executor which uses a priority queue as a work queue. The jobs that will be submitted will be treated
 * as {@link PrioritizedRunnable} and/or {@link PrioritizedCallable}, those tasks that are not instances of these two will
 * be wrapped and assign a default {@link Priority#NORMAL} priority.
 * 

* Note, if two tasks have the same priority, the first to arrive will be executed first (FIFO style). */ public class PrioritizedEsThreadPoolExecutor extends EsThreadPoolExecutor { private static final TimeValue NO_WAIT_TIME_VALUE = TimeValue.timeValueMillis(0); private final AtomicLong insertionOrder = new AtomicLong(); private final Queue current = ConcurrentCollections.newQueue(); private final ScheduledExecutorService timer; private final StarvationWatcher starvationWatcher; public PrioritizedEsThreadPoolExecutor( String name, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, ThreadContext contextHolder, ScheduledExecutorService timer, StarvationWatcher starvationWatcher ) { super(name, corePoolSize, maximumPoolSize, keepAliveTime, unit, new PriorityBlockingQueue<>(), threadFactory, contextHolder); this.timer = timer; this.starvationWatcher = starvationWatcher; } public Pending[] getPending() { List pending = new ArrayList<>(); addPending(new ArrayList<>(current), pending, true); addPending(new ArrayList<>(getQueue()), pending, false); return pending.toArray(new Pending[pending.size()]); } public int getNumberOfPendingTasks() { int size = current.size(); size += getQueue().size(); return size; } /** * Returns the waiting time of the first task in the queue */ public TimeValue getMaxTaskWaitTime() { if (getQueue().size() == 0) { return NO_WAIT_TIME_VALUE; } long now = System.nanoTime(); long oldestCreationDateInNanos = now; for (Runnable queuedRunnable : getQueue()) { if (queuedRunnable instanceof PrioritizedRunnable) { oldestCreationDateInNanos = Math.min( oldestCreationDateInNanos, ((PrioritizedRunnable) queuedRunnable).getCreationDateInNanos() ); } } return TimeValue.timeValueNanos(now - oldestCreationDateInNanos); } private void addPending(List runnables, List pending, boolean executing) { for (Runnable runnable : runnables) { if (runnable instanceof TieBreakingPrioritizedRunnable t) { Runnable innerRunnable = t.runnable; if (innerRunnable != null) { /** innerRunnable can be null if task is finished but not removed from executor yet, * see {@link TieBreakingPrioritizedRunnable#run} and {@link TieBreakingPrioritizedRunnable#runAndClean} */ pending.add(new Pending(super.unwrap(innerRunnable), t.priority(), t.insertionOrder, executing)); } } else if (runnable instanceof PrioritizedFutureTask t) { Object task = t.task; if (t.task instanceof Runnable) { task = super.unwrap((Runnable) t.task); } pending.add(new Pending(task, t.priority, t.insertionOrder, executing)); } } } @Override protected void beforeExecute(Thread t, Runnable r) { current.add(r); if (getQueue().isEmpty()) { starvationWatcher.onEmptyQueue(); } } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); current.remove(r); if (getQueue().isEmpty()) { starvationWatcher.onEmptyQueue(); } else { starvationWatcher.onNonemptyQueue(); } } public void execute(Runnable command, final TimeValue timeout, final Runnable timeoutCallback) { command = wrapRunnable(command); execute(command); if (timeout.nanos() >= 0) { if (command instanceof TieBreakingPrioritizedRunnable) { ((TieBreakingPrioritizedRunnable) command).scheduleTimeout(timer, timeoutCallback, timeout); } else { // We really shouldn't be here. The only way we can get here if somebody created PrioritizedFutureTask // and passed it to execute, which doesn't make much sense throw new UnsupportedOperationException("Execute with timeout is not supported for future tasks"); } } } @Override protected Runnable wrapRunnable(Runnable command) { if (command instanceof PrioritizedRunnable) { if (command instanceof TieBreakingPrioritizedRunnable) { return command; } Priority priority = ((PrioritizedRunnable) command).priority(); return new TieBreakingPrioritizedRunnable(super.wrapRunnable(command), priority, insertionOrder.incrementAndGet()); } else if (command instanceof PrioritizedFutureTask) { return command; } else { // it might be a callable wrapper... return new TieBreakingPrioritizedRunnable(super.wrapRunnable(command), Priority.NORMAL, insertionOrder.incrementAndGet()); } } @Override protected Runnable unwrap(Runnable runnable) { if (runnable instanceof WrappedRunnable) { return super.unwrap(((WrappedRunnable) runnable).unwrap()); } else { return super.unwrap(runnable); } } @Override protected RunnableFuture newTaskFor(Runnable runnable, T value) { if ((runnable instanceof PrioritizedRunnable) == false) { runnable = PrioritizedRunnable.wrap(runnable, Priority.NORMAL); } Priority priority = ((PrioritizedRunnable) runnable).priority(); return new PrioritizedFutureTask<>(runnable, priority, value, insertionOrder.incrementAndGet()); } @Override protected RunnableFuture newTaskFor(Callable callable) { if ((callable instanceof PrioritizedCallable) == false) { callable = PrioritizedCallable.wrap(callable, Priority.NORMAL); } return new PrioritizedFutureTask((PrioritizedCallable) callable, insertionOrder.incrementAndGet()); } public static class Pending { public final Object task; public final Priority priority; public final long insertionOrder; public final boolean executing; public Pending(Object task, Priority priority, long insertionOrder, boolean executing) { this.task = task; this.priority = priority; this.insertionOrder = insertionOrder; this.executing = executing; } } private final class TieBreakingPrioritizedRunnable extends PrioritizedRunnable implements WrappedRunnable { private Runnable runnable; private final long insertionOrder; // these two variables are protected by 'this' private ScheduledFuture timeoutFuture; private boolean started = false; TieBreakingPrioritizedRunnable(Runnable runnable, Priority priority, long insertionOrder) { super(priority); this.runnable = runnable; this.insertionOrder = insertionOrder; } @Override public void run() { synchronized (this) { // make the task as stared. This is needed for synchronization with the timeout handling // see #scheduleTimeout() started = true; FutureUtils.cancel(timeoutFuture); } runAndClean(runnable); } @Override public int compareTo(PrioritizedRunnable pr) { int res = super.compareTo(pr); if (res != 0 || (pr instanceof TieBreakingPrioritizedRunnable) == false) { return res; } return insertionOrder < ((TieBreakingPrioritizedRunnable) pr).insertionOrder ? -1 : 1; } public void scheduleTimeout(ScheduledExecutorService timer, final Runnable timeoutCallback, TimeValue timeValue) { synchronized (this) { if (timeoutFuture != null) { throw new IllegalStateException("scheduleTimeout may only be called once"); } if (started == false) { timeoutFuture = timer.schedule(new Runnable() { @Override public void run() { if (remove(TieBreakingPrioritizedRunnable.this)) { runAndClean(timeoutCallback); } } }, timeValue.nanos(), TimeUnit.NANOSECONDS); } } } /** * Timeout callback might remain in the timer scheduling queue for some time and it might hold * the pointers to other objects. As a result it's possible to run out of memory if a large number of * tasks are executed */ private void runAndClean(Runnable run) { try { run.run(); } finally { runnable = null; timeoutFuture = null; } } @Override public Runnable unwrap() { return runnable; } } private static final class PrioritizedFutureTask extends FutureTask implements Comparable> { final Object task; final Priority priority; final long insertionOrder; PrioritizedFutureTask(Runnable runnable, Priority priority, T value, long insertionOrder) { super(runnable, value); this.task = runnable; this.priority = priority; this.insertionOrder = insertionOrder; } PrioritizedFutureTask(PrioritizedCallable callable, long insertionOrder) { super(callable); this.task = callable; this.priority = callable.priority(); this.insertionOrder = insertionOrder; } @Override public int compareTo(PrioritizedFutureTask pft) { int res = priority.compareTo(pft.priority); if (res != 0) { return res; } return insertionOrder < pft.insertionOrder ? -1 : 1; } } /** * We expect the work queue to be empty fairly frequently; if the queue remains nonempty for sufficiently long then there's a risk that * some lower-priority tasks are being starved of access to the executor. Implementations of this interface are notified whether the * work queue is empty or not before and after execution of each task, so that we can warn the user of this possible starvation. */ public interface StarvationWatcher { /** * Called before and after the execution of each task if the queue is empty (excluding the task being executed) */ void onEmptyQueue(); /** * Called after the execution of each task if the queue is nonempty (excluding the task being executed) */ void onNonemptyQueue(); StarvationWatcher NOOP_STARVATION_WATCHER = new StarvationWatcher() { @Override public void onEmptyQueue() {} @Override public void onNonemptyQueue() {} }; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy