package com.feedly.cassandra.dao;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.MBeanServer;
import me.prettyprint.hector.api.Keyspace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feedly.cassandra.entity.EntityMetadata;
import com.feedly.cassandra.entity.IndexMetadata;
/**
* corrects index "offline", i.e. asynchronously. A thread pool is configured to handle the updates. If the thread pool gets overwhelmed,
* updates are simply dropped.
*
* @author kireet
*/
public class OfflineRepairStrategy implements IStaleIndexValueStrategy
{
private static final Logger _logger = LoggerFactory.getLogger(OfflineRepairStrategy.class.getName());
private static final AtomicInteger _threadId = new AtomicInteger();
private InlineRepairStrategy _strategy;
private int _threadCount = 1;
private int _maxQueueSize = 1000;
private ThreadPoolExecutor _executor;
public void init()
{
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(_maxQueueSize);
_strategy = new InlineRepairStrategy();
_strategy.init();
final OfflineRepairStrategyMonitor monitor = new OfflineRepairStrategyMonitor(_strategy.stats(), queue);
_executor = new ThreadPoolExecutor(1, _threadCount, 10, TimeUnit.SECONDS,
queue,
new ThreadFactory()
{
public Thread newThread(Runnable r)
{
return new Thread(r, "offline-repair-strategy-" + _threadId.incrementAndGet());
}
},
new RejectedExecutionHandler()
{
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
{
_logger.warn("repair queue full, dropping update");
monitor.rejectedExecution();
}
});
try
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(monitor, MBeanUtils.mBeanName(this, null, "repairStats"));
_logger.info("monitoring registration complete for {}", getClass().getSimpleName());
}
catch(Exception e)
{
_logger.warn("error registering mbeans", e);
}
}
public void destroy()
{
_executor.shutdown();
try
{
_executor.awaitTermination(60, TimeUnit.SECONDS);
}
catch(Exception ex)
{
_logger.warn("repair queue not emptied after 60 seconds, continuing system shutdown");
_executor.shutdownNow();
}
try
{
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.unregisterMBean(MBeanUtils.mBeanName(this, null, "repairStats"));
_logger.info("monitoring unregistration complete for {}", getClass().getSimpleName());
}
catch(Exception e)
{
_logger.warn("error unregistering mbeans", e);
}
}
/**
* set the thread pool size.
* @param threadCount
*/
public void setThreadCount(int threadCount)
{
_threadCount = threadCount;
}
/**
* set the thread pool queue size. If the queue is full, repair operations are dropped.
* @param queueSize
*/
public void setMaxQueueSize(int queueSize)
{
_maxQueueSize = queueSize;
}
@Override
public void handle(final EntityMetadata<?> entity, final IndexMetadata index, final Keyspace keyspace, final Collection<StaleIndexValue> values)
{
try
{
_executor.submit(
new Runnable()
{
@Override
public void run()
{
_strategy.handle(entity, index, keyspace, values);
}
});
}
catch(RejectedExecutionException ree)
{
_logger.warn("system shutting down, dropping repair update");
}
}
}