io.prestosql.jdbc.$internal.airlift.concurrent.BoundedExecutor Maven / Gradle / Ivy
The newest version!
package io.prestosql.jdbc.$internal.airlift.concurrent;
import io.prestosql.jdbc.$internal.guava.base.Preconditions;
import io.prestosql.jdbc.$internal.airlift.log.Logger;
import io.prestosql.jdbc.$internal.javax.annotation.concurrent.ThreadSafe;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Objects.requireNonNull;
/**
* Guarantees that no more than maxThreads will be used to execute tasks submitted
* through {@link #execute(Runnable) execute()}.
*
* There are a few interesting properties:
*
* - Multiple BoundedExecutors over a single coreExecutor will have fair sharing
* of the coreExecutor threads proportional to their relative maxThread counts, but
* can use less if not as active.
* - Tasks submitted to a BoundedExecutor is guaranteed to have those tasks
* handed to threads in that order.
* - Will not encounter starvation
*
*/
@ThreadSafe
public class BoundedExecutor
implements Executor
{
private static final Logger log = Logger.get(BoundedExecutor.class);
private final Queue queue = new ConcurrentLinkedQueue<>();
private final AtomicInteger queueSize = new AtomicInteger(0);
private final AtomicBoolean failed = new AtomicBoolean();
private final Executor coreExecutor;
private final int maxThreads;
public BoundedExecutor(Executor coreExecutor, int maxThreads)
{
requireNonNull(coreExecutor, "coreExecutor is null");
Preconditions.checkArgument(maxThreads > 0, "maxThreads must be greater than zero");
this.coreExecutor = coreExecutor;
this.maxThreads = maxThreads;
}
@Override
public void execute(Runnable task)
{
if (failed.get()) {
throw new RejectedExecutionException("BoundedExecutor is in a failed state");
}
queue.add(task);
int size = queueSize.incrementAndGet();
if (size <= maxThreads) {
// If able to grab a permit, then we are short exactly one draining thread
try {
coreExecutor.execute(this::drainQueue);
}
catch (Throwable e) {
failed.set(true);
log.error("BoundedExecutor state corrupted due to underlying executor failure");
throw e;
}
}
}
private void drainQueue()
{
// INVARIANT: queue has at least one task available when this method is called
do {
try {
queue.poll().run();
}
catch (Throwable e) {
log.error(e, "Task failed");
}
}
while (queueSize.getAndDecrement() > maxThreads);
}
}