package edu.berkeley.lipstick.backend;
import edu.berkeley.lipstick.config.Config;
import edu.berkeley.lipstick.localstore.ILocalStore;
import edu.berkeley.lipstick.storage.IStorage;
import edu.berkeley.lipstick.util.*;
import edu.berkeley.lipstick.util.serializer.IDWSerializer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class VersionApplier {
ConcurrentHashMap<String, DataWrapper> bufferedWrites = new ConcurrentHashMap<String, DataWrapper>();
ILocalStore localStore;
IStorage remoteStore;
IDWSerializer serializer;
AsynchronousResolver resolver;
final int maxBufferedWrites;
public VersionApplier(ILocalStore localStore,
IStorage remoteStore,
AsynchronousResolver resolver) throws Exception {
this.localStore = localStore;
this.remoteStore = remoteStore;
serializer = Config.getDWSerializer();
this.resolver = resolver;
maxBufferedWrites = Config.getMaxBufferedWrites();
}
private void bufferWrite(DataWrapper wrapper) {
if(bufferedWrites.size() < maxBufferedWrites)
bufferedWrites.put(wrapper.getKey(), wrapper);
}
private void unbufferWrite(DataWrapper wrapper) {
DataWrapper curWrapper = bufferedWrites.get(wrapper.getKey());
if(curWrapper == wrapper)
bufferedWrites.remove(wrapper.getKey());
}
private DataWrapper getBufferedWrite(String key) {
return bufferedWrites.get(key);
}
public void addToCheck(DataWrapper newWrapper) throws Exception {
bufferWrite(newWrapper);
}
public boolean checkSingleKey(DataWrapper checkWrapper, int numECDSchecks) throws Exception {
Vector<DataWrapper> keysToApply = new Vector<DataWrapper>();
if(!attemptToCover(checkWrapper, keysToApply, numECDSchecks))
return false;
if(keysToApply.size() == 0)
return true;
for(DataWrapper wrapper : keysToApply) {
localStore.put(wrapper.getKey(), wrapper);
unbufferWrite(wrapper);
}
return true;
}
private void addToConsider(String key, WriteClock wc, Map<String, List<WriteClock>> consider) {
List<WriteClock> wrappers = consider.get(key);
if(wrappers == null) {
wrappers = new ArrayList<WriteClock>();
consider.put(key, wrappers);
}
wrappers.add(wc);
}
private boolean attemptToCover(DataWrapper toCheck, List<DataWrapper> keysToApply, final int numECDSreads) throws Exception {
Map<String, List<WriteClock>> toConsider = new HashMap<String, List<WriteClock>>();
addToConsider(toCheck.getKey(), toCheck.getWriteClock(), toConsider);
return attemptToCover(toCheck, keysToApply, toConsider, numECDSreads);
}
private boolean attemptToCover(DataWrapper toCheck,
List<DataWrapper> keysToApply,
Map<String, List<WriteClock>> extraWritesToConsider,
final int remainingECDSdepth) throws Exception {
// see if we've already applied this one
DataWrapper localWrapper = localStore.get(toCheck.getKey());
if(localWrapper != null) {
int causality = localWrapper.getWriteClock().compareToClock(toCheck.getWriteClock());
if(causality == WriteClock.HAPPENS_AFTER || causality == WriteClock.IS_EQUAL)
return true;
}
for(String keyDep : toCheck.getKeyDependencySet().getKeys()) {
if(keyDep.equals(toCheck.getKey()))
continue;
DataWrapper storedWrapper = localStore.get(keyDep);
WriteClock depClock = toCheck.getDependency(keyDep).getClock();
//if we've already applied this write or a write that will overwrite this write, we're good
if(storedWrapper != null
&& storedWrapper.getWriteClock().compareToClock(depClock) != WriteClock.HAPPENS_BEFORE) {
addToConsider(keyDep, depClock, extraWritesToConsider);
continue;
}
// if we've already checked that we have a suitable cover for this value
if(extraWritesToConsider.containsKey(keyDep)) {
boolean found = false;
for(WriteClock alreadyApplied : extraWritesToConsider.get(keyDep)) {
if(alreadyApplied.compareToClock(depClock) != WriteClock.HAPPENS_BEFORE) {
//don't include this in to consider since we're already using another
//dependency (alreadyApplied!)
found = true;
break;
}
}
if(found)
continue;
}
if(remainingECDSdepth <= 0) {
resolver.addKeyToCheck(keyDep);
return false;
}
//now check any buffered writes
DataWrapper bufferedWrapper = getBufferedWrite(keyDep);
if(bufferedWrapper != null) {
int causality = bufferedWrapper.getWriteClock().compareToClock(depClock);
if(causality == WriteClock.IS_EQUAL ||
((causality == WriteClock.HAPPENS_AFTER || causality == WriteClock.IS_CONCURRENT)
&& (attemptToCover(bufferedWrapper, keysToApply, extraWritesToConsider, remainingECDSdepth)))) {
addToConsider(keyDep, depClock, extraWritesToConsider);
keysToApply.add(bufferedWrapper);
continue;
}
}
//now try to read from the underlying store...
byte[] readWriteBytes = remoteStore.get(keyDep);
if(readWriteBytes == null)
return false;
DataWrapper newlyReadWrite = serializer.fromByteArray(keyDep, readWriteBytes);
int causality = newlyReadWrite.getWriteClock().compareToClock(depClock);
if(causality == WriteClock.IS_EQUAL ||
((causality == WriteClock.HAPPENS_AFTER || causality == WriteClock.IS_CONCURRENT)
&& (attemptToCover(newlyReadWrite, keysToApply, extraWritesToConsider, remainingECDSdepth-1)))) {
addToConsider(keyDep, depClock, extraWritesToConsider);
keysToApply.add(newlyReadWrite);
continue;
}
else
addToCheck(newlyReadWrite);
return false;
}
keysToApply.add(toCheck);
return true;
}
public void applyAllPossible() throws Exception {
while(true) {
boolean changed = false;
ArrayList<DataWrapper> writesToConsider = new ArrayList<DataWrapper>(bufferedWrites.values());
for(DataWrapper frontier : writesToConsider) {
Vector<DataWrapper> keysToApply = new Vector<DataWrapper>();
boolean covered = attemptToCover(frontier, keysToApply, Integer.MAX_VALUE);
if(!covered)
continue;
for(DataWrapper wrapper : keysToApply) {
localStore.put(wrapper.getKey(), wrapper);
unbufferWrite(wrapper);
changed = true;
}
if(changed)
break;
}
if(!changed)
break;
}
}
}