/*
* Copyright 2014, Stratio.
*
* Licensed 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 com.stratio.cassandra.util;
import com.stratio.cassandra.contrib.NotifyingBlockingThreadPoolExecutor;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A queue that executes each submitted task using one of possibly several pooled threads. Tasks can be submitted with
* an identifier, ensuring that all tasks with same identifier will be executed orderly in the same thread. Each thread
* has its own task queue.
*
* @author Andres de la Pena <adelapena@stratio.com>
*/
public class TaskQueue {
private NotifyingBlockingThreadPoolExecutor[] pools;
private ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* Returns a new {@link TaskQueue}
*
* @param numThreads The number of executor threads.
* @param queuesSize The max number of tasks in each thread queue before blocking.
*/
public TaskQueue(int numThreads, int queuesSize) {
pools = new NotifyingBlockingThreadPoolExecutor[numThreads];
for (int i = 0; i < numThreads; i++) {
pools[i] = new NotifyingBlockingThreadPoolExecutor(1,
queuesSize,
Long.MAX_VALUE,
TimeUnit.DAYS,
0,
TimeUnit.NANOSECONDS,
null);
pools[i].submit(new Runnable() {
@Override
public void run() {
Log.debug("Task queue starts");
}
});
}
}
/**
* Submits a non value-returning task for asynchronous execution.
* <p/>
* The specified identifier is used to choose the thread executor where the task will be queued. The selection and
* load balancing is based in the {@link #hashCode()} of this identifier.
*
* @param id The identifier of the task used to choose the thread executor where the task will be queued for
* asynchronous execution.
* @param task A task to be queued for asynchronous execution.
*/
public Future<?> submitAsynchronous(Object id, Runnable task) {
lock.readLock().lock();
try {
int i = Math.abs(id.hashCode() % pools.length);
return pools[i].submit(task);
} catch (Exception e) {
Log.error(e, "Task queue submission failed");
throw new RuntimeException(e);
} finally {
lock.readLock().unlock();
}
}
private void awaitInner() throws ExecutionException, InterruptedException {
Future<?>[] futures = new Future<?>[pools.length];
for (int i = 0; i < pools.length; i++) {
Future<?> future = pools[i].submit(new Runnable() {
@Override
public void run() {
}
});
futures[i] = future;
}
for (Future<?> future : futures) {
future.get();
}
}
public void await() {
lock.writeLock().lock();
try {
awaitInner();
} catch (InterruptedException e) {
Log.error(e, "Await interrupted");
throw new RuntimeException(e);
} catch (ExecutionException e) {
Log.error(e, "Await failed");
throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
}
}
/**
* Submits a non value-returning task for synchronous execution. It waits for all synchronous tasks to be
* completed.
*
* @param task A task to be executed synchronously.
*/
public void submitSynchronous(Runnable task) {
lock.writeLock().lock();
try {
awaitInner();
task.run();
} catch (InterruptedException e) {
Log.error(e, "Task queue isolated submission interrupted");
throw new RuntimeException(e);
} catch (Exception e) {
Log.error(e, "Task queue isolated submission failed");
throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
}
}
}