package org.corfudb.infrastructure.log;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.corfudb.protocols.wireprotocol.LogData;
import org.corfudb.runtime.exceptions.OverwriteException;
import org.corfudb.runtime.exceptions.TrimmedException;
/**
* This class implements the StreamLog interface using a Java hash map. The stream log is only stored in-memory and not
* persisted and thus should only be used for testing.
*
* Created by maithem on 7/21/16.
*/
public class InMemoryStreamLog implements StreamLog, StreamLogWithRankedAddressSpace {
private Map<Long, LogData> logCache;
private Map<UUID, Map<Long, LogData>> streamCache;
private Set<LogAddress> trimmed;
final private AtomicLong globalTail = new AtomicLong(0L);
public InMemoryStreamLog() {
logCache = new ConcurrentHashMap();
streamCache = new HashMap();
trimmed = new HashSet();
}
@Override
public synchronized void append(LogAddress logAddress, LogData entry) {
if (logAddress.getStream() == null) {
if(logCache.containsKey(logAddress.address)) {
throwLogUnitExceptionsIfNecessary(logAddress, entry);
}
logCache.put(logAddress.address, entry);
} else {
Map<Long, LogData> stream = streamCache.get(logAddress.getStream());
if(stream == null) {
stream = new HashMap();
streamCache.put(logAddress.getStream(), stream);
}
if(stream.containsKey(logAddress.address)) {
throwLogUnitExceptionsIfNecessary(logAddress, entry);
}
stream.put(logAddress.address, entry);
}
globalTail.getAndUpdate(maxTail -> entry.getGlobalAddress() > maxTail ? entry.getGlobalAddress() : maxTail);
}
@Override
public long getGlobalTail() {
return globalTail.get();
}
private void throwLogUnitExceptionsIfNecessary(LogAddress logAddress, LogData entry) {
if (entry.getRank()==null) {
throw new OverwriteException();
} else {
// the method below might throw DataOutrankedException or ValueAdoptedException
assertAppendPermittedUnsafe(logAddress, entry);
}
}
@Override
public synchronized void trim(LogAddress logAddress) {
trimmed.add(logAddress);
}
@Override
public LogData read(LogAddress logAddress) {
if(trimmed.contains(logAddress)) {
throw new TrimmedException();
}
if (logAddress.getStream() == null) {
return logCache.get(logAddress.address);
} else {
Map<Long, LogData> stream = streamCache.get(logAddress.getStream());
if (stream == null) {
return null;
} else {
return stream.get(logAddress.address);
}
}
}
@Override
public void sync(boolean force){
//no-op
}
@Override
public void close() {
logCache = new HashMap();
streamCache = new HashMap();
}
@Override
public void release(LogAddress logAddress, LogData entry) {
// in memory, do nothing
}
@Override
public void compact() {
for (LogAddress logAddress : trimmed) {
if(logAddress.getStream() == null) {
logCache.remove(logAddress.address);
} else {
Map<Long, LogData> stream = streamCache.get(logAddress.getStream());
if(stream != null){
stream.remove(logAddress.address);
}
}
}
}
}