package com.netflix.evcache.pool;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicIntProperty;
import com.netflix.evcache.EVCacheLatch;
import com.netflix.evcache.EVCacheLatch.Policy;
import com.netflix.evcache.metrics.EVCacheMetricsFactory;
import com.netflix.evcache.operation.EVCacheLatchImpl;
import com.netflix.evcache.operation.EVCacheOperationFuture;
import com.netflix.evcache.util.EVCacheConfig;
import com.netflix.servo.DefaultMonitorRegistry;
import com.netflix.servo.monitor.CompositeMonitor;
import com.netflix.servo.monitor.Monitors;
import com.netflix.spectator.api.DistributionSummary;
import net.spy.memcached.CachedData;
import net.spy.memcached.internal.OperationFuture;
import net.spy.memcached.ops.StatusCode;
public class EVCacheClientUtil {
private static Logger log = LoggerFactory.getLogger(EVCacheClientUtil.class);
private final ChunkTranscoder ct = new ChunkTranscoder();
private final String _appName;
private final DistributionSummary addDataSizeSummary;
private final DistributionSummary addTTLSummary;
private final DynamicBooleanProperty fixup;
private final DynamicIntProperty fixupPoolSize;
private final EVCacheClientPool _pool;
private ThreadPoolExecutor threadPool = null;
public EVCacheClientUtil(EVCacheClientPool pool) {
this._pool = pool;
this._appName = pool.getAppName();
this.addDataSizeSummary = EVCacheMetricsFactory.getDistributionSummary(_appName + "-AddData-Size", _appName, null);
this.addTTLSummary = EVCacheMetricsFactory.getDistributionSummary(_appName + "-AddData-TTL", _appName, null);
this.fixup = EVCacheConfig.getInstance().getDynamicBooleanProperty(_appName + ".addOperation.fixup", Boolean.FALSE);
this.fixupPoolSize = EVCacheConfig.getInstance().getDynamicIntProperty(_appName + ".addOperation.fixup.poolsize", 10);
RejectedExecutionHandler block = new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
EVCacheMetricsFactory.increment(_appName , null, null, _appName + "-AddCall-FixUp-REJECTED");
}
};
class SimpleThreadFactory implements ThreadFactory {
private final AtomicInteger counter = new AtomicInteger();
public Thread newThread(Runnable r) {
return new Thread(r, "EVCacheClientUtil-AddFixUp-" + counter.getAndIncrement());
}
}
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10000);
threadPool = new ThreadPoolExecutor(fixupPoolSize.get(), fixupPoolSize.get() * 2, 30, TimeUnit.SECONDS, queue, new SimpleThreadFactory(), block);
CompositeMonitor<?> newThreadPoolMonitor = Monitors.newThreadPoolMonitor("EVCacheClientUtil-AddFixUp", threadPool);
DefaultMonitorRegistry.getInstance().register(newThreadPoolMonitor);
threadPool.prestartAllCoreThreads();
}
public EVCacheLatch add(String canonicalKey, CachedData cd, int timeToLive, Policy policy) throws Exception {
if (cd == null) return null;
addDataSizeSummary.record(cd.getData().length);
addTTLSummary.record(timeToLive);
final EVCacheClient[] clients = _pool.getEVCacheClientForWrite();
final EVCacheLatchImpl latch = new EVCacheLatchImpl(policy, clients.length - _pool.getWriteOnlyEVCacheClients().length, _appName){
@Override
public void onComplete(OperationFuture<?> operationFuture) throws Exception {
super.onComplete(operationFuture);
if (getPendingFutureCount() == 0 && fixup.get()) {
final RemoteRequest req = new RemoteRequest(this, canonicalKey, timeToLive);
threadPool.submit(req);
}
}
};
for (EVCacheClient client : clients) {
final Future<Boolean> future = client.add(canonicalKey, timeToLive, cd, ct, latch);
if(log.isDebugEnabled()) log.debug("ADD : Op Submitted : APP " + _appName + ", key " + canonicalKey + "; future : " + future);
}
return latch;
}
class RemoteRequest implements Runnable {
private EVCacheLatchImpl latch;
private String canonicalKey;
private int timeToLive;
public RemoteRequest(EVCacheLatchImpl latch, String canonicalKey, int timeToLive) {
this.latch = latch;
this.canonicalKey = canonicalKey;
this.timeToLive = timeToLive;
}
public void run() {
final List<Future<Boolean>> futures = latch.getAllFutures();
int successCount = 0, failCount = 0;
for(int i = 0; i < futures.size() ; i++) {
final Future<Boolean> future = futures.get(i);
if(future instanceof EVCacheOperationFuture) {
final EVCacheOperationFuture<Boolean> f = (EVCacheOperationFuture<Boolean>)future;
if(f.getStatus().getStatusCode() == StatusCode.SUCCESS) {
successCount++;
if(log.isDebugEnabled()) log.debug("ADD : Success : APP " + _appName + ", key " + canonicalKey+ ", ServerGroup : " + f.getServerGroup().getName());
} else {
failCount++;
if(log.isDebugEnabled()) log.debug("ADD : Fail : APP " + _appName + ", key : " + canonicalKey + ", ServerGroup : " + f.getServerGroup().getName());
}
}
}
if(log.isDebugEnabled()) log.debug("ADD : Status: APP " + _appName + ", key : " + canonicalKey + ", failCount : " + failCount + "; successCount : " + successCount);
if(successCount > 0 && failCount > 0) {
CachedData readData = null;
for(int i = 0; i < futures.size(); i++) {
final Future<Boolean> evFuture = futures.get(i);
if(evFuture instanceof EVCacheOperationFuture) {
final EVCacheOperationFuture<Boolean> f = (EVCacheOperationFuture<Boolean>)evFuture;
if(f.getStatus().getStatusCode() == StatusCode.ERR_EXISTS) {
final EVCacheClient client = _pool.getEVCacheClient(f.getServerGroup());
if(client != null) {
try {
readData = client.get(canonicalKey, ct, false, false);
} catch (Exception e) {
log.error("Error reading the data", e);
}
if(log.isDebugEnabled()) log.debug("Add : Read existing data for: APP " + _appName + ", key " + canonicalKey + "; ServerGroup : " + client.getServerGroupName());
if(readData != null) {
break;
} else {
}
}
}
}
}
if(readData != null) {
for(int i = 0; i < futures.size(); i++) {
final Future<Boolean> evFuture = futures.get(i);
if(evFuture instanceof OperationFuture) {
final EVCacheOperationFuture<Boolean> f = (EVCacheOperationFuture<Boolean>)evFuture;
if(f.getStatus().getStatusCode() == StatusCode.SUCCESS) {
final EVCacheClient client = _pool.getEVCacheClient(f.getServerGroup());
if(client != null) {
try {
client.set(canonicalKey, readData, timeToLive, null);
if(log.isDebugEnabled()) log.debug("Add: Fixup for : APP " + _appName + ", key " + canonicalKey + "; ServerGroup : " + client.getServerGroupName());
EVCacheMetricsFactory.increment(_appName , null, client.getServerGroupName(), _appName + "-AddCall-FixUp");
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug("Add: Fixup Error : APP " + _appName + ", key " + canonicalKey + "; ServerGroup : " + client.getServerGroupName(), e);
}
}
}
}
}
}
}
}
}
}