/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.ambari.server.controller.utilities;
import java.util.Queue;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* An {@link ExecutorCompletionService} which takes a {@link ThreadPoolExecutor}
* and uses its thread pool to execute tasks - buffering any tasks that
* overflow. Such buffered tasks are later re-submitted to the executor when
* finished tasks are polled or taken.
* <p/>
* This class overrides the {@link ThreadPoolExecutor}'s
* {@link RejectedExecutionHandler} to collect overflowing tasks. When
* {@link #poll(long, TimeUnit)} is invoked, this class will attempt to
* re-submit any overflow tasks before waiting the specified amount of time.
* This will prevent blocking on an empty completion queue since the parent
* {@link ExecutorCompletionService} doesn't have any idea that there are tasks
* waiting to be resubmitted.
* <p/>
* The {@link ScalingThreadPoolExecutor} can be used in conjunction with this
* class to provide an efficient buffered, scaling thread pool implementation.
*
* @param <V>
*/
public class BufferedThreadPoolExecutorCompletionService<V> extends ExecutorCompletionService<V> {
private ThreadPoolExecutor executor;
private Queue<Runnable> overflowQueue;
public BufferedThreadPoolExecutorCompletionService(ThreadPoolExecutor executor) {
super(executor);
this.executor = executor;
this.overflowQueue = new LinkedBlockingQueue<>();
this.executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
/**
* Once the ThreadPoolExecutor is at full capacity, it starts to reject
* submissions which are queued for later submission.
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
overflowQueue.add(r);
}
});
}
@Override
public Future<V> take() throws InterruptedException {
Future<V> take = super.take();
if (!executor.isTerminating() && !overflowQueue.isEmpty() && executor.getActiveCount() < executor.getMaximumPoolSize()) {
Runnable overflow = overflowQueue.poll();
if (overflow != null) {
executor.execute(overflow);
}
}
return take;
}
@Override
public Future<V> poll() {
Future<V> poll = super.poll();
if (!executor.isTerminating() && !overflowQueue.isEmpty() && executor.getActiveCount() < executor.getMaximumPoolSize()) {
Runnable overflow = overflowQueue.poll();
if (overflow != null) {
executor.execute(overflow);
}
}
return poll;
}
/**
* {@inheritDoc}
* <p/>
* The goal of this method is to prevent blocking if there are tasks waiting
* in the overflow queue. In the event that the overflow queue was populated,
* we should not blindly wait on the parent
* {@link ExecutorCompletionService#poll(long, TimeUnit)} method. Instead, we
* should ensure that we have submitted at least one of our own tasks for
* completion.
*/
@Override
public Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException {
// first poll for anything that's already completed and do a short-circuit return
Future<V> poll = super.poll();
if( null != poll ) {
// there's something to return; that's great, but let's also see if we can
// submit anything in the overflow queue back to the completion service
if (!executor.isTerminating() && !overflowQueue.isEmpty()
&& executor.getActiveCount() < executor.getMaximumPoolSize()) {
Runnable overflow = overflowQueue.poll();
if (overflow != null) {
executor.execute(overflow);
}
}
// return the future
return poll;
}
// nothing completed yet, so check active thread count - if there is nothing
// working either, then that means that the parent completion service thinks
// it's done - we should submit our own tasks
if (executor.getActiveCount() == 0) {
if (!executor.isTerminating() && !overflowQueue.isEmpty()) {
Runnable overflow = overflowQueue.poll();
if (overflow != null) {
executor.execute(overflow);
}
}
}
// now that we've confirmed that either the parent completion service is
// still working or we submitted our own task, we can poll with a timeout
poll = super.poll(timeout, unit);
return poll;
}
}