package org.radargun.stages.cache;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.radargun.DistStageAck;
import org.radargun.StageResult;
import org.radargun.config.Property;
import org.radargun.config.Stage;
import org.radargun.state.SlaveState;
import org.radargun.traits.BasicOperations;
import org.radargun.traits.InjectTrait;
import org.radargun.traits.Transactional;
import org.radargun.utils.Projections;
import org.radargun.utils.TimeConverter;
/**
* Stage checking the write skew detection in transactional caches.
*
* @author Radim Vansa <rvansa@redhat.com>
*/
@Stage(doc = "Stage checking the write skew detection in transactional caches.")
public class WriteSkewCheckStage extends CheckStage {
private static final String WRITE_SKEW_CHECK_KEY = "writeSkewCheckKey";
@Property(converter = TimeConverter.class, doc = "Duration of the test. Default is 1 minute.")
private long duration = 60000;
@Property(doc = "Number of threads overwriting concurrently the entry. Default is 10.")
private int threads = 10;
@Property(doc = "Should write skew between null value and first value be tested? Default is false.")
private boolean testNull = false;
private volatile boolean finished = false;
private AtomicLong totalCounter = new AtomicLong(0);
private AtomicLong skewCounter = new AtomicLong(0);
@InjectTrait
private BasicOperations basicOperations;
@InjectTrait
private Transactional transactional;
@Override
public DistStageAck executeOnSlave() {
BasicOperations.Cache cache = basicOperations.getCache(null);
if (!testNull) {
try {
cache.put(WRITE_SKEW_CHECK_KEY, new Long(0));
} catch (Exception e) {
return errorResponse("Failed to insert initial zero", e);
}
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
log.warn("Sleep was interrupted");
}
}
List<ClientThread> threadList = new ArrayList<ClientThread>();
for (int i = 0; i < threads; ++i) {
ClientThread t = new WriteSkewThread();
t.start();
threadList.add(t);
}
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
log.warn("Sleep was interrupted");
}
finished = true;
DistStageAck error = checkThreads(threadList);
if (error != null) return error;
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
log.warn("Sleep was interrupted");
}
long ispnCounter;
try {
Object value = cache.get(WRITE_SKEW_CHECK_KEY);
if (!(value instanceof Long)) {
return errorResponse("Counter is not a long: it is " + value);
} else {
ispnCounter = (Long) value;
}
} catch (Exception e) {
return errorResponse("Failed to insert first value", e);
}
return new Counters(slaveState, totalCounter.get(), skewCounter.get(), ispnCounter);
}
@Override
public StageResult processAckOnMaster(List<DistStageAck> acks) {
StageResult result = super.processAckOnMaster(acks);
if (result.isError()) return result;
long sumIncrements = 0;
long sumSkews = 0;
long maxValue = -1;
for (Counters ack : Projections.instancesOf(acks, Counters.class)) {
sumIncrements += ack.totalCounter;
sumSkews += ack.skewCounter;
maxValue = Math.max(maxValue, ack.ispnCounter);
}
log.infof("Total increments: %d, Skews: %d, DB value: %d", sumIncrements, sumSkews, maxValue);
if (maxValue + sumSkews != sumIncrements) {
log.errorf("Database contains value %d but slaves report %d successful increments",
maxValue, sumIncrements - sumSkews);
return errorResult();
} else {
log.infof("Performed %d successful increments in %d ms", sumIncrements - sumSkews, duration);
return StageResult.SUCCESS;
}
}
private static class Counters extends DistStageAck {
long totalCounter;
long skewCounter;
long ispnCounter;
private Counters(SlaveState slaveState, long totalCounter, long skewCounter, long ispnCounter) {
super(slaveState);
this.totalCounter = totalCounter;
this.skewCounter = skewCounter;
this.ispnCounter = ispnCounter;
}
@Override
public String toString() {
return "Counters{" +
"totalCounter=" + totalCounter +
", skewCounter=" + skewCounter +
", ispnCounter=" + ispnCounter +
"} " + super.toString();
}
}
private class WriteSkewThread extends ClientThread {
@Override
public void run() {
BasicOperations.Cache nonTxCache = basicOperations.getCache(null);
while (!finished) {
Transactional.Transaction tx = transactional.getTransaction();
BasicOperations.Cache txCache = tx.wrap(nonTxCache);
log.trace("Starting transaction");
tx.begin();
Object value = null;
try {
value = txCache.get(WRITE_SKEW_CHECK_KEY);
if (value == null) {
value = new Long(0);
} else if (!(value instanceof Long)) {
exception = new IllegalStateException("Counter is not a long: it is " + value);
return;
}
txCache.put(WRITE_SKEW_CHECK_KEY, ((Long) value) + 1);
} catch (Exception e) {
exception = e;
return;
}
boolean skew = false;
try {
tx.commit();
} catch (Exception e) {
log.trace("Skew detected");
skewCounter.incrementAndGet();
skew = true;
}
if (!skew && ((Long) value).longValue() == 0l) {
log.trace("Successfully inserted 1");
}
totalCounter.incrementAndGet();
log.trace("Ended transaction");
}
}
}
}