final List<Node> nodes = availableNodes(routingStrategy.routeRequest(key.get()));
// quickly fail if there aren't enough nodes to meet the requirement
final int numNodes = nodes.size();
if(numNodes < this.storeDef.getRequiredWrites())
throw new InsufficientOperationalNodesException("Only " + numNodes
+ " nodes in preference list, but "
+ this.storeDef.getRequiredWrites()
+ " writes required.");
// A count of the number of successful operations
final AtomicInteger successes = new AtomicInteger(0);
// A list of thrown exceptions, indicating the number of failures
final List<Exception> failures = Collections.synchronizedList(new ArrayList<Exception>(1));
// If requiredWrites > 0 then do a single blocking write to the first
// live node in the preference list if this node throws an
// ObsoleteVersionException allow it to propagate
Node master = null;
int currentNode = 0;
Versioned<byte[]> versionedCopy = null;
for(; currentNode < numNodes; currentNode++) {
Node current = nodes.get(currentNode);
long startNsLocal = System.nanoTime();
try {
versionedCopy = incremented(versioned, current.getId());
innerStores.get(current.getId()).put(key, versionedCopy, transforms);
successes.getAndIncrement();
recordSuccess(current, startNsLocal);
master = current;
break;
} catch(UnreachableStoreException e) {
recordException(current, startNsLocal, e);
failures.add(e);
} catch(VoldemortApplicationException e) {
throw e;
} catch(Exception e) {
failures.add(e);
}
}
if(successes.get() < 1)
throw new InsufficientOperationalNodesException("No master node succeeded!",
failures.size() > 0 ? failures.get(0)
: null);
else
currentNode++;
// A semaphore indicating the number of completed operations
// Once inititialized all permits are acquired, after that
// permits are released when an operation is completed.
// semaphore.acquire(n) waits for n operations to complete
final Versioned<byte[]> finalVersionedCopy = versionedCopy;
final Semaphore semaphore = new Semaphore(0, false);
// Add the operations to the pool
int attempts = 0;
for(; currentNode < numNodes; currentNode++) {
attempts++;
final Node node = nodes.get(currentNode);
this.executor.execute(new Runnable() {
@Override
public void run() {
long startNsLocal = System.nanoTime();
try {
innerStores.get(node.getId()).put(key, finalVersionedCopy, transforms);
successes.incrementAndGet();
recordSuccess(node, startNsLocal);
} catch(UnreachableStoreException e) {
recordException(node, startNsLocal, e);
failures.add(e);
} catch(ObsoleteVersionException e) {
// ignore this completely here
// this means that a higher version was able
// to write on this node and should be termed as clean
// success.
} catch(VoldemortApplicationException e) {
throw e;
} catch(Exception e) {
logger.warn("Error in PUT on node " + node.getId() + "(" + node.getHost()
+ ")", e);
failures.add(e);
} finally {
// signal that the operation is complete
semaphore.release();
}
}
});
}
// Block until we get enough completions
int blockCount = Math.min(storeDef.getPreferredWrites() - 1, attempts);
boolean noTimeout = blockOnPut(startNs,
semaphore,
0,
blockCount,
successes,
storeDef.getPreferredWrites());
if(successes.get() < storeDef.getRequiredWrites()) {
/*
* We don't have enough required writes, but we haven't timed out
* yet, so block a little more if there are healthy nodes that can
* help us achieve our target.
*/
if(noTimeout) {
int startingIndex = blockCount - 1;
blockCount = Math.max(storeDef.getPreferredWrites() - 1, attempts);
blockOnPut(startNs,
semaphore,
startingIndex,
blockCount,
successes,
storeDef.getRequiredWrites());
}
if(successes.get() < storeDef.getRequiredWrites())
throw new InsufficientOperationalNodesException(successes.get()
+ " writes succeeded, but "
+ this.storeDef.getRequiredWrites()
+ " are required.", failures);
}