package com.rubiconproject.oss.kv.distributed.impl; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.rubiconproject.oss.kv.distributed.InsufficientResponsesException; import com.rubiconproject.oss.kv.distributed.Node; import com.rubiconproject.oss.kv.distributed.Operation; import com.rubiconproject.oss.kv.distributed.OperationCallback; import com.rubiconproject.oss.kv.distributed.OperationQueue; import com.rubiconproject.oss.kv.distributed.OperationResult; import com.rubiconproject.oss.kv.distributed.OperationStatus; public class DefaultOperationHelper { private OperationLog operationLog = OperationLog.getInstance(); private Log log = LogFactory.getLog(getClass()); public <V> ResultsCollecter<OperationResult<V>> call( OperationQueue operationQueue, Operation<V> operation, List<Node> nodeList, int nodeRankOffset, int requiredResponses, long operationTimeout, boolean considerNullAsSuccess, boolean throwInsufficientResponsesException) throws InsufficientResponsesException { long start = System.currentTimeMillis(); operationLog.logPreferenceList(operation.getKey(), nodeList); LinkedList<Future<OperationResult<V>>> futures = new LinkedList<Future<OperationResult<V>>>(); AtomicInteger successCounter = new AtomicInteger(0); ResultsCollecter<OperationResult<V>> resultCollecter = new ResultsCollecter<OperationResult<V>>( nodeList.size()); CountDownLatch latch = new CountDownLatch(requiredResponses); OperationCallback<V> callback = new OperationCallback<V>() { private CountDownLatch latch; private AtomicInteger successCounter; private boolean considerNullAsSuccess; private ResultsCollecter<OperationResult<V>> resultCollecter; public OperationCallback<V> init(CountDownLatch latch, AtomicInteger successCounter, boolean considerNullAsSuccess, ResultsCollecter<OperationResult<V>> resultCollecter) { this.latch = latch; this.successCounter = successCounter; this.considerNullAsSuccess = considerNullAsSuccess; this.resultCollecter = resultCollecter; return this; } public void completed(OperationResult<V> result) { resultCollecter.add(result); latch.countDown(); if (result.getStatus().equals(OperationStatus.Success)) { successCounter.incrementAndGet(); } if ((result.getStatus().equals(OperationStatus.NullValue)) && (considerNullAsSuccess)) { successCounter.incrementAndGet(); } } }.init(latch, successCounter, considerNullAsSuccess, resultCollecter); for (int i = 0; i < nodeList.size(); ++i) { Operation<V> op = operation.copy(); op.setCallback(callback); op.setNode(nodeList.get(i)); op.setNodeRank(i + nodeRankOffset); Future<OperationResult<V>> future = operationQueue.submit(op); futures.add(future); } try { latch.await(operationTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } // stop waiting if any of the following occur // 1) successful response from r/w nodes // 2) response completes successfully or not from n nodes // 3) timeout exceeded while ((successCounter.get() < requiredResponses) && (resultCollecter.size() < nodeList.size()) && ((System.currentTimeMillis() - start) < operationTimeout)) { try { Future<OperationResult<V>> future = futures.pop(); if (future != null) { try { OperationResult<V> result = future.get(operationTimeout - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.info("TimeoutException waiting on response", e); } catch (ExecutionException e) { log.info("ExecutionException waiting on response", e); } catch (Exception e) { log.info("Exception waiting on response", e); } } } catch (NoSuchElementException e) { break; } } if ((successCounter.get() < requiredResponses) && (throwInsufficientResponsesException)) { throw new InsufficientResponsesException(requiredResponses, successCounter.get()); } return resultCollecter; } }