/*
* Copyright (c) 2008-2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.coordinator.client.service.impl;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.coordinator.client.service.DistributedQueueItemProcessedCallback;
/**
* Abstract Distributed Queue Consumer Class
* Note:
* 1. Each inherited consumer needs to implement the consumeItem();
* 2. Each inherited consumer who wants to use its own thread pool needs to
* override isBusy() to get load balance.
*/
public abstract class DistributedQueueConsumer<T>
{
private static final Logger _log = LoggerFactory.getLogger(DistributedQueueConsumer.class);
// distributed queue map
private final ConcurrentMap<String, DistributedQueueImpl<T>> _distQueueMap = new ConcurrentHashMap<String, DistributedQueueImpl<T>>();
private volatile ThreadPoolExecutor _consumers = null; // default consumer thread pool
private volatile int _maxThreads = 10; // max threads of consumer thread pool
private AtomicInteger _curItems = new AtomicInteger(0);// number of the tasks scheduled
private AtomicInteger _totalItems = new AtomicInteger(0);// number of total tasks scheduled
private AtomicLong _totalTimeSpent = new AtomicLong(0); // total time spent for tasks
/**
* Initialize the distributed queue consumer
*
* @param queue queue name
* @param distQueue distributed queue instance
* Some consumers (e.g. Dispather) include several distributed queues.
* @param maxThreads max threads of consumer thread pool
*/
void init(String queue, DistributedQueueImpl<T> distQueue, int maxThreads) {
_maxThreads = maxThreads;
if (_consumers == null) {
_consumers = new ThreadPoolExecutor(_maxThreads, _maxThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
}
_distQueueMap.putIfAbsent(queue, distQueue);
}
/**
* Start to process an item from the distributed queue
* It would change task count, and schedule it within the consumer thread pool.
*
* @param queue Name of the queue
* @param itemname Name of the item to process
* @param item Data of the item to process
*
*/
void startConsumeItem(final String queue, final String itemname, final T item) {
if (_consumers == null) {
return;
}
final int count = _curItems.incrementAndGet();
_totalItems.incrementAndGet();
try {
_consumers.execute(new Runnable() {
@Override
public void run() {
long startMillis = 0, endMillis = 0;
try {
startMillis = System.currentTimeMillis();
_log.debug("consuming a new task: number of tasks = {}", count);
DistributedQueueItemProcessedCallbackImpl itemProcessedCallback =
new DistributedQueueItemProcessedCallbackImpl(queue, itemname);
consumeItem(item, itemProcessedCallback);
} catch (Exception e) {
_log.error("Failed to consume item.", e);
_curItems.decrementAndGet();
} finally {
endMillis = System.currentTimeMillis();
_totalTimeSpent.getAndAdd(endMillis - startMillis);
}
}
});
} catch (Exception e) {
_log.error("Failed to schedule item.", e);
_curItems.decrementAndGet();
}
}
/**
* DistributedQueueItemProcessedCallback implementer that calls DistributedQueue's remove method
* to delete the item with name _itemName, from the queue.
*/
private class DistributedQueueItemProcessedCallbackImpl implements DistributedQueueItemProcessedCallback {
private final String _queueName;
private final String _itemName;
public DistributedQueueItemProcessedCallbackImpl(String queue, String itemName) {
_queueName = queue;
_itemName = itemName;
}
@Override
public void itemProcessed() throws Exception {
DistributedQueueImpl<T> _distQueue = _distQueueMap.get(_queueName);
synchronized (_distQueue) {
_curItems.decrementAndGet();
_distQueue.notifyAll();
}
_distQueue.remove(_itemName);
}
@Override
public String toString() {
return "DistributedQueueItem:" + _itemName;
}
}
/**
* uninitialize the consumer
* shutdown the consumer thread pool.
*
* @param timeoutMs time to wait during termination
*
* @return true for success, otherwise false
*/
boolean uninit(long timeoutMs) {
if (_consumers == null) {
return true;
}
try {
_log.info("total request: {}, time spent: {}", _totalItems.get(), _totalTimeSpent.get());
_consumers.shutdownNow();
if (_consumers.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS)) {
_log.info("Consumer stopped");
return true;
}
return false;
} catch (InterruptedException e) {
_log.warn("Consumer stopping interrupted", e);
return false;
}
}
/**
* Check if consumer is busy
* Note:
* Each inherited consumer who wants to use its own thread pool needs to
* override isBusy() to get load balance.
*
* @param queue queue path
*
* @return true for busy, otherwise false
*/
public boolean isBusy(String queue) {
return (_curItems.get() >= _maxThreads) ? true : false;
}
/**
* Process an item from the distributed queue
*
* @param item Item to process
* @param callback This must be executed, after the item is processed successfully to remove the item
* from the distributed queue
*
* @throws Exception any errors
*/
public abstract void consumeItem(T item, DistributedQueueItemProcessedCallback callback) throws Exception;
}