/*
* @(#)ParallelShardOperationCallable.java 2012-8-1 下午10:00:00
*
* Copyright (c) 2011-2012 Makersoft.org all rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
*
*/
package org.makersoft.shards.strategy.access.impl;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.makersoft.shards.Shard;
import org.makersoft.shards.ShardId;
import org.makersoft.shards.ShardOperation;
import org.makersoft.shards.strategy.exit.ExitStrategy;
/**
* Runs a single operation on a single shard, collecting the result of the
* operation with an ExitStrategy. The interesting bit here is that
* if the ExitStrategy indicates that there is no more work to be performed,
* this object has the ability to cancel the work being performed by all the
* other threads.
*
*/
class ParallelShardOperationCallable<T> implements Callable<Void> {
private static final boolean INTERRUPT_IF_RUNNING = false;
private final Log log = LogFactory.getLog(getClass());
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final ExitStrategy<T> exitStrategy;
private final ShardOperation<T> operation;
private final Shard shard;
private final ShardId shardId;
private final List<StartAwareFutureTask> futureTasks;
public ParallelShardOperationCallable(
CountDownLatch startSignal,
CountDownLatch doneSignal,
ExitStrategy<T> exitStrategy,
ShardOperation<T> operation,
Shard shard,
ShardId shardId,
List<StartAwareFutureTask> futureTasks) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.exitStrategy = exitStrategy;
this.operation = operation;
this.shard = shard;
this.shardId = shardId;
this.futureTasks = futureTasks;
}
public Void call() throws Exception {
try {
waitForStartSignal();
log.debug(String.format("Starting execution of %s against shard %s", operation.getOperationName(), shard));
/**
* If addResult() returns true it means there is no more work to be
* performed. Cancel all the outstanding tasks.
*/
if(exitStrategy.addResult(operation.execute(shard.establishSqlSession(), shardId), shard)) {
log.debug(
String.format(
"Short-circuiting execution of %s on other threads after execution against shard %s",
operation.getOperationName(),
shard));
/**
* It's ok to cancel ourselves because StartAwareFutureTask.cancel()
* will return false if a task has already started executing, and we're
* already executing.
*/
log.debug(String.format("Checking %d future tasks to see if they need to be cancelled.", futureTasks.size()));
for(StartAwareFutureTask ft : futureTasks) {
log.debug(String.format("Preparing to cancel future task %d.", ft.getId()));
/**
* If a task was successfully cancelled that means it had not yet
* started running. Since the task won't run, the task won't be
* able to decrement the CountDownLatch. We need to decrement
* it on behalf of the cancelled task.
*/
if(ft.cancel(INTERRUPT_IF_RUNNING)) {
log.debug("Task cancel returned true, decrementing counter on its behalf.");
doneSignal.countDown();
} else {
log.debug("Task cancel returned false, not decrementing counter on its behalf.");
}
}
} else {
log.debug(
String.format(
"No need to short-cirtcuit execution of %s on other threads after execution against shard %s",
operation.getOperationName(),
shard));
}
} finally {
// counter must get decremented no matter what
log.debug(String.format("Decrementing counter for operation %s on shard %s", operation.getOperationName(), shard));
doneSignal.countDown();
}
return null;
}
private void waitForStartSignal() {
try {
startSignal.await();
} catch (InterruptedException e) {
// I see no reason why this should happen
final String msg = String.format("Received interrupt while waiting to begin execution of %s against shard %s", operation.getOperationName(), shard);
log.error(msg);
throw new RuntimeException(msg);
}
}
}