/*
* Copyright 2015 Higher Frequency Trading
*
* http://www.higherfrequencytrading.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.openhft.chronicle.engine.map;
import net.openhft.chronicle.bytes.BytesStore;
import net.openhft.chronicle.core.values.IntValue;
import net.openhft.chronicle.engine.api.EngineReplication;
import net.openhft.chronicle.engine.api.map.KeyValueStore;
import net.openhft.chronicle.engine.api.map.MapEventListener;
import net.openhft.chronicle.engine.api.map.SubscriptionKeyValueStore;
import net.openhft.chronicle.wire.Marshallable;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import net.openhft.lang.collection.ATSDirectBitSet;
import net.openhft.lang.collection.DirectBitSet;
import net.openhft.lang.io.DirectStore;
import net.openhft.lang.model.DataValueClasses;
import net.openhft.lang.model.constraints.MaxSize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import static net.openhft.chronicle.engine.map.VanillaEngineReplication.ReplicationData.*;
public class VanillaEngineReplication<K, V, MV, Store extends SubscriptionKeyValueStore<K, MV>>
implements EngineReplication, Closeable {
public static final int RESERVED_MOD_ITER = 8;
public static final int MAX_MODIFICATION_ITERATORS = 127 + RESERVED_MOD_ITER;
// a long word serve 64 bits
public static final int DIRTY_WORD_COUNT = (MAX_MODIFICATION_ITERATORS + 63) / 64;
@NotNull
private static final ThreadLocal<Instances> threadLocalInstances =
ThreadLocal.withInitial(Instances::new);
@NotNull
private final KeyValueStore<BytesStore, ReplicationData>[] keyReplicationData;
@NotNull
private final KeyValueStore<IntValue, RemoteNodeReplicationState>
modIterState;
private final byte identifier;
@NotNull
private final Store store;
private final ChangeApplier<Store> changeApplier;
private final GetValue<Store> getValue;
private final SegmentForKey<Store> segmentForKey;
private final AtomicReferenceArray<VanillaModificationIterator>
modificationIterators = new AtomicReferenceArray<>(127 + RESERVED_MOD_ITER);
private final DirectBitSet modificationIteratorsRequiringSettingBootstrapTimestamp =
createModIterBitSet();
private final DirectBitSet modIterSet = createModIterBitSet();
@NotNull
private final MapEventListener<K, MV> eventListener;
public VanillaEngineReplication(
@NotNull IntFunction<KeyValueStore<BytesStore, ReplicationData>>
obtainKeyReplicationDataBySegment,
@NotNull KeyValueStore<IntValue, RemoteNodeReplicationState>
modIterState,
byte identifier,
@NotNull Store store, ChangeApplier<Store> changeApplier, GetValue<Store> getValue,
SegmentForKey<Store> segmentForKey,
@NotNull Function<K, BytesStore> keyToBytesStore) {
int segments = store.segments();
this.keyReplicationData = new KeyValueStore[segments];
for (int i = 0; i < segments; i++) {
keyReplicationData[i] = obtainKeyReplicationDataBySegment.apply(i);
}
this.modIterState = modIterState;
initZeroStateForAllPossibleRemoteIdentifiers(modIterState);
this.identifier = identifier;
this.store = store;
this.changeApplier = changeApplier;
this.getValue = getValue;
this.segmentForKey = segmentForKey;
eventListener = new MapEventListener<K, MV>() {
@Override
public void insert(String assetName, K key, MV value) {
onPut(keyToBytesStore.apply(key), System.currentTimeMillis());
}
@Override
public void remove(String assetName, K key, MV value) {
onRemove(keyToBytesStore.apply(key), System.currentTimeMillis());
}
@Override
public void update(String assetName, K key, MV oldValue, MV newValue) {
onPut(keyToBytesStore.apply(key), System.currentTimeMillis());
}
};
store.subscription(true).registerDownstream(e -> e.apply(eventListener));
}
private static int idToInt(byte identifier) {
// if we consider > 127 ids, we should treat ids positively
return identifier & 0xFF;
}
@NotNull
private static DirectBitSet createModIterBitSet() {
return ATSDirectBitSet.wrap(new DirectStore(null, DIRTY_WORD_COUNT * 8, true).bytes());
}
private static void initZeroStateForAllPossibleRemoteIdentifiers(
@NotNull KeyValueStore<IntValue, RemoteNodeReplicationState>
modIterState) {
Instances i = threadLocalInstances.get();
for (int id = 0; id < 256; id++) {
i.identifier.setValue(id);
modIterState.put(i.identifier, i.zeroState);
}
}
private static boolean shouldApplyRemoteModification(
@NotNull ReplicationEntry remoteEntry, @NotNull ReplicationData localReplicationData) {
long remoteTimestamp = remoteEntry.timestamp();
long originTimestamp = localReplicationData.getTimestamp();
return remoteTimestamp > originTimestamp || (remoteTimestamp == originTimestamp &&
remoteEntry.identifier() <= localReplicationData.getIdentifier());
}
@Override
public byte identifier() {
return identifier;
}
private void resetNextBootstrapTimestamp(int remoteIdentifier) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(remoteIdentifier);
while (true) {
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
i.copyState.copyFrom(i.usingState);
i.copyState.setNextBootstrapTimestamp(0);
if (modIterState.replaceIfEqual(i.identifier, i.usingState, i.copyState))
return;
}
}
private boolean setNextBootstrapTimestamp(int remoteIdentifier, long timestamp) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(remoteIdentifier);
while (true) {
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
if (i.usingState.getNextBootstrapTimestamp() != 0)
return false;
i.copyState.copyFrom(i.usingState);
i.copyState.setNextBootstrapTimestamp(0);
if (modIterState.replaceIfEqual(i.identifier, i.usingState, i.copyState))
return true;
}
}
private void resetLastBootstrapTimestamp(int remoteIdentifier) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(remoteIdentifier);
while (true) {
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
i.copyState.copyFrom(i.usingState);
i.copyState.setLastBootstrapTimestamp(0);
if (modIterState.replaceIfEqual(i.identifier, i.usingState, i.copyState))
return;
}
}
private long bootstrapTimestamp(int remoteIdentifier) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(remoteIdentifier);
while (true) {
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
long nextBootstrapTs = i.usingState.getNextBootstrapTimestamp();
if (nextBootstrapTs == 0) {
return i.usingState.getLastBootstrapTimestamp();
} else {
i.copyState.copyFrom(i.usingState);
i.copyState.setLastBootstrapTimestamp(nextBootstrapTs);
if (modIterState.replaceIfEqual(i.identifier, i.usingState, i.copyState))
return nextBootstrapTs;
}
}
}
@Override
public long lastModificationTime(byte remoteIdentifier) {
return lastModificationTime(idToInt(remoteIdentifier));
}
////////////////
// Method for working with modIterState
private long lastModificationTime(int remoteIdentifier) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(remoteIdentifier);
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
return i.usingState.getLastModificationTime();
}
@Override
public void setLastModificationTime(byte identifier, long timestamp) {
setLastModificationTime(idToInt(identifier), timestamp);
}
private void setLastModificationTime(int identifier, long timestamp) {
Instances i = threadLocalInstances.get();
i.identifier.setValue(identifier);
while (true) {
i.usingState = modIterState.getUsing(i.identifier, i.usingState);
if (i.usingState.getLastModificationTime() < timestamp) {
i.copyState.copyFrom(i.usingState);
i.copyState.setLastModificationTime(timestamp);
if (modIterState.replaceIfEqual(i.identifier, i.usingState, i.copyState))
return;
} else {
return;
}
}
}
@Override
public void applyReplication(@NotNull ReplicationEntry replicatedEntry) {
Instances i = threadLocalInstances.get();
@Nullable BytesStore key = replicatedEntry.key();
while (true) {
KeyValueStore<BytesStore, ReplicationData> keyReplicationData =
this.keyReplicationData[segmentForKey.segmentForKey(store, key)];
@Nullable ReplicationData data = keyReplicationData.getUsing(key, i.usingData);
if (data != null)
i.usingData = data;
boolean shouldApplyRemoteModification = data == null ||
shouldApplyRemoteModification(replicatedEntry, data);
if (shouldApplyRemoteModification) {
i.newData.copyFrom(data != null ? data : i.zeroData);
changeApplier.applyChange(store, replicatedEntry);
i.newData.setDeleted(replicatedEntry.isDeleted());
i.newData.setIdentifier(replicatedEntry.identifier());
i.newData.setTimestamp(replicatedEntry.timestamp());
if (data == null) {
if (keyReplicationData.putIfAbsent(key, i.newData) == null)
return;
} else {
dropChange(i.newData);
if (keyReplicationData.replaceIfEqual(key, data, i.newData))
return;
}
}
}
}
@Override
public ModificationIterator acquireModificationIterator(byte id) {
int remoteIdentifier = idToInt(id);
ModificationIterator modificationIterator = modificationIterators.get(remoteIdentifier);
if (modificationIterator != null)
return modificationIterator;
synchronized (modificationIterators) {
modificationIterator = modificationIterators.get(remoteIdentifier);
if (modificationIterator != null)
return modificationIterator;
@NotNull final VanillaModificationIterator newModificationIterator =
new VanillaModificationIterator(remoteIdentifier);
modificationIteratorsRequiringSettingBootstrapTimestamp.set(remoteIdentifier);
resetNextBootstrapTimestamp(remoteIdentifier);
// in ChMap 2.1 currentTime() is set as a default lastBsTs; set to 0 here; TODO review
resetLastBootstrapTimestamp(remoteIdentifier);
modificationIterators.set(remoteIdentifier, newModificationIterator);
modIterSet.set(remoteIdentifier);
return newModificationIterator;
}
}
public void onPut(BytesStore key, long putTimestamp) {
onChange(key, false, putTimestamp);
}
public void onRemove(BytesStore key, long removeTimestamp) {
onChange(key, true, removeTimestamp);
}
private void onChange(BytesStore key, boolean deleted, long changeTimestamp) {
Instances i = threadLocalInstances.get();
while (true) {
KeyValueStore<BytesStore, ReplicationData> keyReplicationData =
this.keyReplicationData[segmentForKey.segmentForKey(store, key)];
@Nullable ReplicationData data = keyReplicationData.getUsing(key, i.usingData);
if (data != null)
i.usingData = data;
i.newData.copyFrom(data != null ? data : i.zeroData);
i.newData.setDeleted(deleted);
long entryTimestamp = i.newData.getTimestamp();
if (entryTimestamp > changeTimestamp)
changeTimestamp = entryTimestamp + 1;
i.newData.setTimestamp(changeTimestamp);
i.newData.setIdentifier(identifier);
raiseChange(i.newData);
boolean successfulUpdate = data == null ?
(keyReplicationData.putIfAbsent(key, i.newData) == null) :
(keyReplicationData.replaceIfEqual(key, data, i.newData));
if (successfulUpdate) {
for (long next = modIterSet.nextSetBit(0L); next > 0L;
next = modIterSet.nextSetBit(next + 1L)) {
VanillaModificationIterator modIter =
modificationIterators.get((int) next);
modIter.modNotify();
if (modificationIteratorsRequiringSettingBootstrapTimestamp.clearIfSet(next)) {
if (!setNextBootstrapTimestamp((int) next, changeTimestamp))
throw new AssertionError();
}
}
return;
}
}
}
@Override
public void close() throws IOException {
try {
@Nullable Throwable throwable = null;
for (@NotNull KeyValueStore<BytesStore, ReplicationData> keyReplicationData :
this.keyReplicationData) {
try {
keyReplicationData.close();
} catch (Throwable e) {
if (throwable == null) {
throwable = e;
} else {
throwable.addSuppressed(e);
}
}
}
if (throwable != null) {
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
throw (RuntimeException) throwable;
}
}
} finally {
modIterState.close();
}
}
public interface ChangeApplier<Store> {
void applyChange(Store store, ReplicationEntry replicationEntry);
}
public interface GetValue<Store> {
@NotNull
BytesStore getValue(Store store, BytesStore key);
}
public interface SegmentForKey<Store> {
int segmentForKey(Store store, BytesStore key);
}
public interface ReplicationData extends Marshallable {
static void dropChange(@NotNull ReplicationData replicationData) {
for (int i = 0; i < DIRTY_WORD_COUNT; i++) {
replicationData.setDirtyWordAt(i, 0);
}
}
static void raiseChange(@NotNull ReplicationData replicationData) {
for (int i = 0; i < DIRTY_WORD_COUNT; i++) {
replicationData.setDirtyWordAt(i, ~0L);
}
}
static void clearChange(@NotNull ReplicationData replicationData, int identifier) {
int index = identifier / 64;
long bit = 1L << (identifier % 64);
replicationData.setDirtyWordAt(index, replicationData.getDirtyWordAt(index) ^ bit);
}
static void setChange(@NotNull ReplicationData replicationData, int identifier) {
int index = identifier / 64;
long bit = 1L << (identifier % 64);
replicationData.setDirtyWordAt(index, replicationData.getDirtyWordAt(index) | bit);
}
static boolean isChanged(@NotNull ReplicationData replicationData, int identifier) {
int index = identifier / 64;
long bit = 1L << (identifier % 64);
return (replicationData.getDirtyWordAt(index) & bit) != 0L;
}
boolean getDeleted();
void setDeleted(boolean deleted);
long getTimestamp();
void setTimestamp(long timestamp);
byte getIdentifier();
void setIdentifier(byte identifier);
long getDirtyWordAt(@MaxSize(DIRTY_WORD_COUNT) int index);
void setDirtyWordAt(@MaxSize(DIRTY_WORD_COUNT) int index, long word);
@Override
default void readMarshallable(@NotNull WireIn wire) throws IllegalStateException {
setDeleted(wire.read(() -> "deleted").bool());
setTimestamp(wire.read(() -> "timestamp").int64());
setIdentifier(wire.read(() -> "identifier").int8());
for (int i = 0; i < DIRTY_WORD_COUNT; i++) {
final int finalI = i;
setDirtyWordAt(i, wire.read(() -> "dirtyWord-" + finalI).int64());
}
}
@Override
default void writeMarshallable(@NotNull WireOut wire) {
wire.write(() -> "deleted").bool(getDeleted());
wire.write(() -> "timestamp").int64(getTimestamp());
wire.write(() -> "identifier").int8(getIdentifier());
for (int i = 0; i < DIRTY_WORD_COUNT; i++) {
final int finalI = i;
wire.write(() -> "dirtyWord-" + finalI).int64(getDirtyWordAt(i));
}
}
}
public interface RemoteNodeReplicationState extends Marshallable {
long getNextBootstrapTimestamp();
void setNextBootstrapTimestamp(long nextBootstrapTimestamp);
long getLastBootstrapTimestamp();
void setLastBootstrapTimestamp(long lastBootstrapTimestamp);
long getLastModificationTime();
void setLastModificationTime(long lastModificationTime);
@Override
default void readMarshallable(@NotNull WireIn wire) throws IllegalStateException {
setNextBootstrapTimestamp(wire.read(() -> "nextBootstrapTimestamp").int64());
setLastBootstrapTimestamp(wire.read(() -> "lastBootstrapTimestamp").int64());
setLastModificationTime(wire.read(() -> "lastModificationTime").int64());
}
@Override
default void writeMarshallable(@NotNull WireOut wire) {
wire.write(() -> "nextBootstrapTimestamp").int64(getNextBootstrapTimestamp());
wire.write(() -> "lastBootstrapTimestamp").int64(getLastBootstrapTimestamp());
wire.write(() -> "lastModificationTime").int64(getLastModificationTime());
}
}
static class Instances {
final IntValue identifier = DataValueClasses.newInstance(IntValue.class);
final RemoteNodeReplicationState copyState =
DataValueClasses.newInstance(RemoteNodeReplicationState.class);
final RemoteNodeReplicationState zeroState =
DataValueClasses.newInstance(RemoteNodeReplicationState.class);
final ReplicationData newData = DataValueClasses.newInstance(ReplicationData.class);
final ReplicationData zeroData = DataValueClasses.newInstance(ReplicationData.class);
@Nullable
RemoteNodeReplicationState usingState = null;
@Nullable
ReplicationData usingData = null;
int keyReplicationDataIndex = -1;
KeyValueStore<BytesStore, ReplicationData> keyReplicationData;
@Nullable
Iterator<BytesStore> keySetIterator;
@Nullable
public Iterator<BytesStore> keySetIterator(@NotNull KeyValueStore<BytesStore, ReplicationData>[] keyReplicationData) {
if (keySetIterator != null && keySetIterator.hasNext())
return keySetIterator;
keySetIterator = null;
keyReplicationDataIndex++;
if (keyReplicationDataIndex == keyReplicationData.length)
keyReplicationDataIndex = 0;
keySetIterator = keyReplicationData[keyReplicationDataIndex].keySetIterator();
return keySetIterator;
}
}
class VanillaModificationIterator implements ModificationIterator, ReplicationEntry {
private final int identifier;
long forEachEntryCount;
ModificationNotifier modificationNotifier;
// Below methods and fields that implement ModIter as ReplicationEntry
@Nullable
BytesStore key;
@Nullable
ReplicationData replicationData;
VanillaModificationIterator(int identifier) {
this.identifier = identifier;
}
@Override
public boolean nextEntry(@NotNull Consumer<ReplicationEntry> consumer) {
Instances i = threadLocalInstances.get();
int count = keyReplicationData.length;
@Nullable Iterator<BytesStore> keySetIterator;
for (; ; ) {
keySetIterator = i.keySetIterator(keyReplicationData);
if (count == 0) {
modificationIteratorsRequiringSettingBootstrapTimestamp.set(identifier);
resetNextBootstrapTimestamp(identifier);
return false;
}
count--;
for (@Nullable Iterator<BytesStore> keyIt = keySetIterator;
keyIt.hasNext(); ) {
BytesStore key = keyIt.next();
i.usingData = i.keyReplicationData.getUsing(key, i.usingData);
if (isChanged(i.usingData, identifier)) {
this.key = key;
this.replicationData = i.usingData;
try {
consumer.accept(this);
i.newData.copyFrom(i.usingData);
clearChange(i.newData, identifier);
if (!i.keyReplicationData.replaceIfEqual(key, i.usingData, i.newData))
throw new AssertionError();
return true;
} finally {
this.key = null;
this.replicationData = null;
}
}
}
}
}
@Override
public boolean hasNext() {
Instances i = threadLocalInstances.get();
for (@NotNull KeyValueStore<BytesStore, ReplicationData> keyReplicationData :
VanillaEngineReplication.this.keyReplicationData) {
for (Iterator<BytesStore> keyIt = keyReplicationData.keySetIterator();
keyIt.hasNext(); ) {
BytesStore key = keyIt.next();
i.usingData = keyReplicationData.getUsing(key, i.usingData);
if (isChanged(i.usingData, identifier))
return true;
}
}
return false;
}
@Override
public void dirtyEntries(long fromTimeStamp) {
Instances i = threadLocalInstances.get();
for (@NotNull KeyValueStore<BytesStore, ReplicationData> keyReplicationData :
VanillaEngineReplication.this.keyReplicationData) {
keyReplicationData.keySetIterator().forEachRemaining(key -> {
i.usingData = keyReplicationData.getUsing(key, i.usingData);
if (i.usingData.getTimestamp() >= fromTimeStamp) {
i.newData.copyFrom(i.usingData);
setChange(i.newData, identifier);
if (!keyReplicationData.replaceIfEqual(key, i.usingData, i.newData))
throw new AssertionError();
}
});
}
}
@Override
public void setModificationNotifier(@NotNull ModificationNotifier modificationNotifier) {
this.modificationNotifier = modificationNotifier;
}
public void modNotify() {
if (modificationNotifier != null)
modificationNotifier.onChange();
}
@Nullable
@Override
public BytesStore key() {
return key;
}
@NotNull
@Override
public BytesStore value() {
return getValue.getValue(store, key);
}
@Override
public long timestamp() {
return replicationData.getTimestamp();
}
@Override
public byte identifier() {
return replicationData.getIdentifier();
}
@Override
public byte remoteIdentifier() {
throw new UnsupportedOperationException("todo");
}
@Override
public boolean isDeleted() {
return replicationData.getDeleted();
}
/**
* @return the timestamp that the remote client should bootstrap from when there has been a
* disconnection, this time maybe later than the message time as event are not send in
* chronological order from the bit set.
*/
@Override
public long bootStrapTimeStamp() {
return bootstrapTimestamp(identifier);
}
}
}