Rework modification iteration and bootstrapping

This commit is contained in:
leventov
2015-12-30 20:38:55 +03:00
parent 19fadf5628
commit b2cd5af700
13 changed files with 486 additions and 392 deletions
@@ -330,23 +330,22 @@ public abstract class VanillaChronicleHash<K,
}
private long segmentSize() {
long ss = tierHashLookupOuterSize + TIER_COUNTERS_AREA_SIZE +
long segmentSize = tierHashLookupOuterSize + TIER_COUNTERS_AREA_SIZE +
tierFreeListOuterSize + tierEntrySpaceOuterSize;
if ((ss & 63L) != 0)
if ((segmentSize & 63L) != 0)
throw new AssertionError();
return breakL1CacheAssociativityContention(ss);
return breakL1CacheAssociativityContention(segmentSize);
}
private long breakL1CacheAssociativityContention(long segmentSize) {
protected final long breakL1CacheAssociativityContention(long sizeInBytes) {
// Conventional alignment to break is 4096 (given Intel's 32KB 8-way L1 cache),
// for any case break 2 times smaller alignment
int alignmentToBreak = 2048;
int eachNthSegmentFallIntoTheSameSet =
max(1, alignmentToBreak >> numberOfTrailingZeros(segmentSize));
if (eachNthSegmentFallIntoTheSameSet < actualSegments) {
segmentSize |= CACHE_LINES.toBytes(1L); // make segment size "odd" (in cache lines)
}
return segmentSize;
max(1, alignmentToBreak >> numberOfTrailingZeros(sizeInBytes));
if (eachNthSegmentFallIntoTheSameSet < actualSegments)
sizeInBytes |= CACHE_LINES.toBytes(1L); // make segment size "odd" (in cache lines)
return sizeInBytes;
}
private long computeNumberOfTiersInBulk() {
@@ -435,7 +435,7 @@ abstract class AbstractChannelReplicator implements Closeable {
}
}
static class EntryCallback extends Replica.EntryCallback {
static class EntryCallback implements Replica.EntryCallback {
private final Replica.EntryExternalizable externalizable;
@@ -459,10 +459,7 @@ abstract class AbstractChannelReplicator implements Closeable {
}
@Override
public boolean onEntry(
ReplicableEntry entry, Bytes payload, int chronicleId, long bootstrapTime) {
long pos0 = entryIn.writePosition();
public void onEntry(ReplicableEntry entry, Bytes payload, int chronicleId) {
// used to denote that this is not a heartbeat
entryIn.writeByte(STATEFUL_UPDATE);
@@ -473,12 +470,7 @@ abstract class AbstractChannelReplicator implements Closeable {
long start = entryIn.writePosition();
externalizable.writeExternalEntry(entry, payload, entryIn, chronicleId, bootstrapTime);
if (entryIn.writePosition() == start) {
entryIn.writePosition(pos0);
return false;
}
externalizable.writeExternalEntry(entry, payload, entryIn, chronicleId);
// write the length of the entry, just before the start, so when we read it back
// we read the length of the entry first and hence know how many preceding writer
@@ -493,7 +485,6 @@ abstract class AbstractChannelReplicator implements Closeable {
LOG.debug("sending entry of entrySize=" + (int) bytesWritten);
entryIn.writeInt(sizeLocation, (int) bytesWritten);
return true;
}
}
@@ -112,19 +112,19 @@ final class ChannelProvider implements Closeable {
@Override
public void writeExternalEntry(ReplicableEntry entry, Bytes payload,
@NotNull Bytes destination,
int chronicleChannel, long bootstrapTime) {
int chronicleChannel) {
channelDataReadLock();
try {
destination.writeStopBit(chronicleChannel);
channelEntryExternalizables[chronicleChannel].writeExternalEntry(
entry, payload, destination, chronicleChannel, bootstrapTime);
entry, payload, destination, chronicleChannel);
} finally {
channelDataLock.readLock().unlock();
}
}
@Override
public void readExternalEntry(@NotNull Bytes source) {
public void readExternalEntry(@NotNull Bytes source, byte remoteNodeIdentifier) {
channelDataReadLock();
try {
final int chronicleId = (int) source.readStopBit();
@@ -132,7 +132,7 @@ final class ChannelProvider implements Closeable {
// this channel is has not currently been created so it updates will be ignored
if (channelEntryExternalizables[chronicleId] != null)
channelEntryExternalizables[chronicleId]
.readExternalEntry(source);
.readExternalEntry(source, remoteNodeIdentifier);
} else
LOG.info("skipped entry with chronicleId=" + chronicleId + ", ");
} finally {
@@ -241,15 +241,15 @@ final class ChannelProvider implements Closeable {
* gets the earliest modification time for all of the chronicles
*/
@Override
public long lastModificationTime(byte remoteIdentifier) {
public long remoteNodeCouldBootstrapFrom(byte remoteIdentifier) {
channelDataReadLock();
try {
long t = 0;
// not including the SystemQueue at index 0
for (int i = 1, len = chronicleChannelList.size(); i < len; i++) {
Replica channel = chronicleChannelList.get(i);
t = (t == 0) ? channel.lastModificationTime(remoteIdentifier) :
min(t, channel.lastModificationTime(remoteIdentifier));
t = (t == 0) ? channel.remoteNodeCouldBootstrapFrom(remoteIdentifier) :
min(t, channel.remoteNodeCouldBootstrapFrom(remoteIdentifier));
}
return t;
} finally {
@@ -258,12 +258,13 @@ final class ChannelProvider implements Closeable {
}
@Override
public void setLastModificationTime(byte identifier, long timestamp) {
public void setRemoteNodeCouldBootstrapFrom(byte remoteIdentifier, long bootstrapTimestamp) {
channelDataReadLock();
try {
// not including the SystemQueue at index 0
for (int i = 1, len = chronicleChannelList.size(); i < len; i++) {
chronicleChannelList.get(i).setLastModificationTime(identifier, timestamp);
chronicleChannelList.get(i)
.setRemoteNodeCouldBootstrapFrom(remoteIdentifier, bootstrapTimestamp);
}
} finally {
@@ -359,27 +360,27 @@ final class ChannelProvider implements Closeable {
private void onBootstrapMessage(Bytes bytes) {
final byte remoteIdentifier = bytes.readByte();
final int chronicleChannel = bytes.readUnsignedShort();
final long lastModificationTime = bytes.readLong();
final long bootstrapFrom = bytes.readLong();
if (LOG.isDebugEnabled())
LOG.debug("received bootstrap message received for localIdentifier=" + this.localIdentifier + ", " +
"remoteIdentifier=" + remoteIdentifier + ",chronicleChannel=" + chronicleChannel + "," +
"lastModificationTime=" + lastModificationTime);
"remoteNodeCouldBootstrapFrom=" + bootstrapFrom);
// this could be null if one node has a chronicle channel before the other
if (chronicleChannels[chronicleChannel] != null) {
chronicleChannels[chronicleChannel].acquireModificationIterator(remoteIdentifier)
.dirtyEntries(lastModificationTime);
.dirtyEntries(bootstrapFrom);
}
}
private static Bytes toBootstrapMessage(
int chronicleChannel, final long lastModificationTime, final byte localIdentifier) {
int chronicleChannel, final long bootstrapFrom, final byte localIdentifier) {
final Bytes writeBuffer = Bytes.wrapForWrite(new byte[1 + 1 + 2 + 8]);
writeBuffer.writeByte(BOOTSTRAP_MESSAGE);
writeBuffer.writeByte(localIdentifier);
writeBuffer.writeUnsignedShort(chronicleChannel);
writeBuffer.writeLong(lastModificationTime);
writeBuffer.writeLong(bootstrapFrom);
return writeBuffer;
}
@@ -409,15 +410,15 @@ final class ChannelProvider implements Closeable {
for (int i = (int) systemModificationIteratorBitSet.nextSetBit(0); i > 0;
i = (int) systemModificationIteratorBitSet.nextSetBit(i + 1)) {
byte remoteIdentifier = (byte) i;
final long lastModificationTime = replica.lastModificationTime(remoteIdentifier);
final long bootstrapFrom = replica.remoteNodeCouldBootstrapFrom(remoteIdentifier);
final Bytes message =
toBootstrapMessage(chronicleChannel, lastModificationTime, localIdentifier);
toBootstrapMessage(chronicleChannel, bootstrapFrom, localIdentifier);
systemModificationIterator.get(remoteIdentifier).addPayload(message);
if (LOG.isDebugEnabled())
LOG.debug("sending bootstrap message received for localIdentifier=" + localIdentifier + ", " +
"remoteIdentifier=" + remoteIdentifier + ",chronicleChannel=" + chronicleChannel + "," +
"lastModificationTime=" + lastModificationTime);
"remoteNodeCouldBootstrapFrom=" + bootstrapFrom);
}
} finally {
channelDataLock.writeLock().unlock();
@@ -490,7 +491,7 @@ final class ChannelProvider implements Closeable {
final Bytes payload = payloads.poll();
if (payload == null)
return false;
callback.onEntry(null, payload, 0, 0);
callback.onEntry(null, payload, 0);
return true;
}
@@ -523,12 +524,12 @@ final class ChannelProvider implements Closeable {
}
@Override
public long lastModificationTime(byte remoteIdentifier) {
public long remoteNodeCouldBootstrapFrom(byte remoteIdentifier) {
return 0;
}
@Override
public void setLastModificationTime(byte identifier, long timestamp) {
public void setRemoteNodeCouldBootstrapFrom(byte remoteIdentifier, long bootstrapTimestamp) {
// do nothing
}
@@ -547,13 +548,13 @@ final class ChannelProvider implements Closeable {
@Override
public void writeExternalEntry(
ReplicableEntry entry, @NotNull Bytes payload, @NotNull Bytes destination,
int na, long bootstrapTime) {
int na) {
destination.write(payload, payload.readPosition(), payload.readRemaining());
}
@Override
public void readExternalEntry(
@NotNull Bytes source) {
@NotNull Bytes source, byte remoteNodeIdentifier) {
messageHandler.onMessage(source);
}
};
@@ -73,9 +73,10 @@ public interface Replica extends Closeable {
* @return a timestamp of the last modification to an entry, or 0 if there are no entries.
* @see #identifier()
*/
long lastModificationTime(byte remoteIdentifier);
long remoteNodeCouldBootstrapFrom(byte remoteIdentifier);
void setLastModificationTime(byte identifier, long timestamp);
void setRemoteNodeCouldBootstrapFrom(
byte remoteIdentifier, long bootstrapTimestamp);
/**
* notifies when there is a changed to the modification iterator
@@ -146,15 +147,14 @@ public interface Replica extends Closeable {
/**
* The map implements this method to save its contents.
*
* @param entry the byte location of the entry to be stored
* @param entry the byte location of the entry to be stored
* @param destination a buffer the entry will be written to, the segment may reject this
* operation and add zeroBytes, if the identifier in the entry did not
* match the maps local
* @param chronicleId is the channel id used to identify the canonical map or queue
*/
void writeExternalEntry(ReplicableEntry entry, Bytes payload, @NotNull Bytes destination,
int chronicleId, long bootstrapTime);
int chronicleId);
/**
* The map implements this method to restore its contents. This method must read the values
@@ -163,15 +163,17 @@ public interface Replica extends Closeable {
* replication event, this event could originate from either a remote {@code put(K key, V
*value)} or {@code remove(Object key)}
* @param source bytes to read an entry from
* @param remoteNodeIdentifier the identifier of the remove node, from which this event came
* (NOT origin id of the entry)
*/
void readExternalEntry(@NotNull Bytes source);
void readExternalEntry(@NotNull Bytes source, byte remoteNodeIdentifier);
}
/**
* Implemented typically by a replicator, This interface provides the event, which will get
* called whenever a put() or remove() has occurred to the map
*/
abstract class EntryCallback {
interface EntryCallback {
/**
* Called whenever a put() or remove() has occurred to a replicating map.
@@ -179,12 +181,8 @@ public interface Replica extends Closeable {
* @param entry the entry you will receive, this does not have to be locked, as
* locking is already provided from the caller.
* @param chronicleId only assigned when clustering
* @return {@code false} if this entry should be ignored because the identifier of the
* source node is not from one of our changes, WARNING even though we check the identifier
* in the ModificationIterator the entry may have been updated.
*/
public abstract boolean onEntry(
ReplicableEntry entry, Bytes payload, int chronicleId, long bootstrapTime);
void onEntry(ReplicableEntry entry, Bytes payload, int chronicleId);
}
}
@@ -18,11 +18,9 @@ package net.openhft.chronicle.map;
import net.openhft.chronicle.algo.bitset.*;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.hash.Data;
import net.openhft.chronicle.hash.VanillaGlobalMutableState;
import net.openhft.chronicle.hash.impl.TierCountersArea;
import net.openhft.chronicle.hash.impl.VanillaChronicleHash;
import net.openhft.chronicle.hash.impl.stage.hash.ChainingInterface;
import net.openhft.chronicle.hash.replication.AbstractReplication;
import net.openhft.chronicle.hash.replication.ReplicableEntry;
@@ -30,6 +28,8 @@ import net.openhft.chronicle.map.impl.CompiledReplicatedMapIterationContext;
import net.openhft.chronicle.map.impl.CompiledReplicatedMapQueryContext;
import net.openhft.chronicle.map.replication.MapRemoteOperations;
import net.openhft.chronicle.values.Values;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,15 +37,16 @@ import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.stream.Stream;
import static net.openhft.chronicle.algo.MemoryUnit.*;
import static net.openhft.chronicle.algo.bitset.BitSetFrame.NOT_FOUND;
import static net.openhft.chronicle.algo.bytes.Access.checkedBytesStoreAccess;
import static net.openhft.chronicle.algo.bytes.Access.nativeAccess;
import static net.openhft.chronicle.hash.replication.TimeProvider.currentTime;
@@ -89,22 +90,31 @@ import static net.openhft.chronicle.hash.replication.TimeProvider.currentTime;
*/
public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R>
implements Replica, Replica.EntryExternalizable {
// for file, jdbc and UDP replication
public static final int RESERVED_MOD_ITER = 8;
public static final int ADDITIONAL_ENTRY_BYTES = 10;
private static final long serialVersionUID = 0L;
private static final Logger LOG = LoggerFactory.getLogger(ReplicatedChronicleMap.class);
private static final long LAST_UPDATED_HEADER_SIZE = 128L * 8L;
private static final int SIZE_OF_BOOTSTRAP_TIME_STAMP = 8;
public static final int ADDITIONAL_ENTRY_BYTES = 10;
static final byte ENTRY_HUNK = 1;
static final byte BOOTSTRAP_TIME_HUNK = 2;
private long tierModIterBitSetSizeInBits;
private long tierModIterBitSetOuterSize;
private long segmentModIterBitSetsForIdentifierOuterSize;
private long tierBulkModIterBitSetsForIdentifierOuterSize;
/**
* Default value is 0, that corresponds to "unset" identifier value (valid ids are positive)
*/
private transient byte localIdentifier;
transient Set<Closeable> closeables;
private transient long identifierUpdatedBytesOffset;
private transient BitSet modIterSet;
/**
* Idiomatically {@code assignedModificationIterators} should be a {@link CopyOnWriteArraySet},
* but we should frequently iterate over this array without creating any garbage,
* that is impossible with {@code CopyOnWriteArraySet}.
*/
private transient ModificationIterator[] assignedModificationIterators;
private transient AtomicReferenceArray<ModificationIterator> modificationIterators;
private transient long startOfModificationIterators;
private transient boolean bootstrapOnlyLocalEntries;
@@ -115,18 +125,47 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
public transient MapRemoteOperations<K, V, R> remoteOperations;
transient BitSetFrame mainSegmentsModIterFrameForUpdates;
transient BitSetFrame mainSegmentsModIterFrameForIteration;
transient BitSetFrame tierBulkModIterFrameForUpdates;
transient BitSetFrame tierBulkModIterFrameForIteration;
transient BitSetFrame tierModIterFrame;
private transient long[] remoteNodeCouldBootstrapFrom;
public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder<K, V> builder,
AbstractReplication replication)
throws IOException {
AbstractReplication replication) throws IOException {
super(builder);
tierModIterBitSetSizeInBits = computeTierModIterBitSetSizeInBits();
tierModIterBitSetOuterSize = computeTierModIterBitSetOuterSize();
segmentModIterBitSetsForIdentifierOuterSize =
computeSegmentModIterBitSetsForIdentifierOuterSize();
tierBulkModIterBitSetsForIdentifierOuterSize =
computeTierBulkModIterBitSetsForIdentifierOuterSize(tiersInBulk);
initTransientsFromReplication(replication);
}
@Override
protected void readMarshallableFields(@NotNull WireIn wireIn) {
super.readMarshallableFields(wireIn);
tierModIterBitSetSizeInBits = wireIn.read(() -> "tierModIterBitSetSizeInBits").int64();
tierModIterBitSetOuterSize = wireIn.read(() -> "tierModIterBitSetOuterSize").int64();
segmentModIterBitSetsForIdentifierOuterSize =
wireIn.read(() -> "segmentModIterBitSetsForIdentifierOuterSize").int64();
tierBulkModIterBitSetsForIdentifierOuterSize =
wireIn.read(() -> "tierBulkModIterBitSetsForIdentifierOuterSize").int64();
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
super.writeMarshallable(wireOut);
wireOut.write(() -> "tierModIterBitSetSizeInBits").int64(tierModIterBitSetSizeInBits);
wireOut.write(() -> "tierModIterBitSetOuterSize").int64(tierModIterBitSetOuterSize);
wireOut.write(() -> "segmentModIterBitSetsForIdentifierOuterSize")
.int64(segmentModIterBitSetsForIdentifierOuterSize);
wireOut.write(() -> "tierBulkModIterBitSetsForIdentifierOuterSize")
.int64(tierBulkModIterBitSetsForIdentifierOuterSize);
}
@Override
protected VanillaGlobalMutableState createGlobalMutableState() {
return Values.newNativeReference(ReplicatedGlobalMutableState.class);
@@ -137,10 +176,6 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
return (ReplicatedGlobalMutableState) super.globalMutableState();
}
private int assignedModIterBitSetSizeInBytes() {
return (int) CACHE_LINES.align(BYTES.alignAndConvert(127 + RESERVED_MOD_ITER, BITS), BYTES);
}
@Override
public void initTransients() {
super.initTransients();
@@ -148,24 +183,18 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
}
private void initOwnTransients() {
modificationIterators =
new AtomicReferenceArray<>(127 + RESERVED_MOD_ITER);
//noinspection unchecked
assignedModificationIterators = new ReplicatedChronicleMap.ModificationIterator[0];
modificationIterators = new AtomicReferenceArray<>(128);
closeables = new CopyOnWriteArraySet<>();
long mainSegmentsBitSetSize = BYTES.toBits(modIterBitSetSizeInBytes());
mainSegmentsModIterFrameForUpdates =
new SingleThreadedFlatBitSetFrame(mainSegmentsBitSetSize);
mainSegmentsModIterFrameForIteration =
new ConcurrentFlatBitSetFrame(mainSegmentsBitSetSize);
long tierBulkBitSetSize =
BYTES.toBits(tierBulkModIterBitSetSizeInBytes(tiersInBulk));
tierBulkModIterFrameForUpdates = new SingleThreadedFlatBitSetFrame(tierBulkBitSetSize);
tierBulkModIterFrameForIteration = new ConcurrentFlatBitSetFrame(tierBulkBitSetSize);
tierModIterFrame = new SingleThreadedFlatBitSetFrame(computeTierModIterBitSetSizeInBits());
remoteNodeCouldBootstrapFrom = new long[128];
}
@Override
void initTransientsFromBuilder(ChronicleMapBuilder<K, V> builder) {
super.initTransientsFromBuilder(builder);
//noinspection unchecked
this.remoteOperations = (MapRemoteOperations<K, V, R>) builder.remoteOperations;
cleanupRemovedEntries = builder.cleanupRemovedEntries;
cleanupTimeout = builder.cleanupTimeout;
@@ -184,65 +213,55 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
initOwnTransients();
}
long modIterBitSetSizeInBytes() {
long bytes = BITS.toBytes(bitsPerSegmentInModIterBitSet() * actualSegments);
return CACHE_LINES.align(bytes, BYTES);
private long computeTierModIterBitSetSizeInBits() {
return LONGS.align(actualChunksPerSegmentTier, BITS);
}
private long computeTierModIterBitSetOuterSize() {
long tierModIterBitSetOuterSize = BYTES.convert(computeTierModIterBitSetSizeInBits(), BITS);
// protect from false sharing between bit sets of adjacent segments
tierModIterBitSetOuterSize += BYTES.convert(2, CACHE_LINES);
if (CACHE_LINES.align(tierModIterBitSetOuterSize, BYTES) == tierModIterBitSetOuterSize) {
tierModIterBitSetOuterSize =
breakL1CacheAssociativityContention(tierModIterBitSetOuterSize);
}
return tierModIterBitSetOuterSize;
}
private long computeSegmentModIterBitSetsForIdentifierOuterSize() {
return computeTierModIterBitSetOuterSize() * actualSegments;
}
private long computeTierBulkModIterBitSetsForIdentifierOuterSize(long tiersInBulk) {
return computeTierModIterBitSetOuterSize() * tiersInBulk;
}
@Override
protected long computeTierBulkInnerOffsetToTiers(long tiersInBulk) {
long tierBulkBitSetsInnerSize =
computeTierBulkModIterBitSetsForIdentifierOuterSize(tiersInBulk) * 128;
return super.computeTierBulkInnerOffsetToTiers(tiersInBulk) +
tierBulkModIterBitSetSizeInBytes(tiersInBulk) * (128 + RESERVED_MOD_ITER);
}
private long tierBulkModIterBitSetSizeInBytes(long numberOfTiersInBulk) {
long bytes = BITS.toBytes(bitsPerSegmentInModIterBitSet() * numberOfTiersInBulk);
return CACHE_LINES.align(bytes, BYTES);
}
private long bitsPerSegmentInModIterBitSet() {
// min 128 * 8 to prevent false sharing on updating bits from different segments
// TODO this doesn't prevent false sharing. There should be GAPS between per-segment bits
return Maths.nextPower2(actualChunksPerSegmentTier, 128L * 8L);
CACHE_LINES.align(tierBulkBitSetsInnerSize, BYTES);
}
@Override
public long mapHeaderInnerSize() {
return super.mapHeaderInnerSize() + LAST_UPDATED_HEADER_SIZE +
assignedModIterBitSetSizeInBytes() +
(modIterBitSetSizeInBytes() * (128 + RESERVED_MOD_ITER));
}
public void setLastModificationTime(byte identifier, long timestamp) {
final long offset = identifierUpdatedBytesOffset + identifier * 8L;
// purposely not volatile as this will impact performance,
// and the worst that will happen is we'll end up loading more data on a bootstrap
if (bs.readLong(offset) < timestamp)
bs.writeLong(offset, timestamp);
return super.mapHeaderInnerSize() + (segmentModIterBitSetsForIdentifierOuterSize * 128);
}
@Override
public long lastModificationTime(byte remoteIdentifier) {
assert remoteIdentifier != this.identifier();
public void setRemoteNodeCouldBootstrapFrom(byte remoteIdentifier, long bootstrapTimestamp) {
remoteNodeCouldBootstrapFrom[remoteIdentifier] = bootstrapTimestamp;
}
// purposely not volatile as this will impact performance,
// and the worst that will happen is we'll end up loading more data on a bootstrap
return bs.readLong(identifierUpdatedBytesOffset + remoteIdentifier * 8L);
@Override
public long remoteNodeCouldBootstrapFrom(byte remoteIdentifier) {
return remoteNodeCouldBootstrapFrom[remoteIdentifier];
}
@Override
public void onHeaderCreated() {
long offset = super.mapHeaderInnerSize();
identifierUpdatedBytesOffset = offset;
offset += LAST_UPDATED_HEADER_SIZE;
ConcurrentFlatBitSetFrame modIterBitSetFrame =
new ConcurrentFlatBitSetFrame(BYTES.toBits(assignedModIterBitSetSizeInBytes()));
modIterSet = new ReusableBitSet(modIterBitSetFrame, checkedBytesStoreAccess(), bs, offset);
offset += assignedModIterBitSetSizeInBytes();
startOfModificationIterators = offset;
startOfModificationIterators = super.mapHeaderInnerSize();
}
@Override
@@ -296,6 +315,7 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
if (modificationIterator != null)
return modificationIterator;
//noinspection SynchronizeOnNonFinalField
synchronized (modificationIterators) {
modificationIterator = modificationIterators.get(remoteIdentifier);
@@ -303,56 +323,122 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
return modificationIterator;
final ModificationIterator modIter = new ModificationIterator(remoteIdentifier);
//noinspection unchecked
assignedModificationIterators = Stream
.concat(Arrays.stream(assignedModificationIterators), Stream.of(modIter))
.sorted(Comparator.comparing(it -> it.remoteIdentifier))
.toArray(ReplicatedChronicleMap.ModificationIterator[]::new);
modificationIterators.set(remoteIdentifier, modIter);
modIterSet.set(remoteIdentifier);
return modIter;
}
}
public void raiseChange(long tierIndex, long pos, long timestamp) {
for (long next = modIterSet.nextSetBit(0L); next > 0L;
next = modIterSet.nextSetBit(next + 1L)) {
try {
acquireModificationIterator((byte) next).raiseChange(tierIndex, pos, timestamp);
} catch (Exception e) {
LOG.error("", e);
public void raiseChange(long tierIndex, long pos) {
if (tierIndex <= actualSegments) {
long segmentIndex = tierIndex - 1;
long offsetToTierBitSet = segmentIndex * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
modificationIterator.raiseChangeInSegment(offsetToTierBitSet, pos);
}
} else {
long extraTierIndex = tierIndex - 1 - actualSegments;
int bulkIndex = (int) (extraTierIndex >> log2TiersInBulk);
long offsetToTierBitSet =
(extraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
modificationIterator.raiseChangeInTierBulk(bulkIndex, offsetToTierBitSet, pos);
}
}
}
public void dropChange(long tierIndex, long pos) {
for (long next = modIterSet.nextSetBit(0L); next > 0L;
next = modIterSet.nextSetBit(next + 1L)) {
try {
acquireModificationIterator((byte) next).dropChange(tierIndex, pos);
} catch (Exception e) {
LOG.error("", e);
if (tierIndex <= actualSegments) {
long segmentIndex = tierIndex - 1;
long offsetToTierBitSet = segmentIndex * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
modificationIterator.dropChangeInSegment(offsetToTierBitSet, pos);
}
} else {
long extraTierIndex = tierIndex - 1 - actualSegments;
int bulkIndex = (int) (extraTierIndex >> log2TiersInBulk);
long offsetToTierBitSet =
(extraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
modificationIterator.dropChangeInTierBulk(bulkIndex, offsetToTierBitSet, pos);
}
}
}
public void moveChange(long oldTierIndex, long oldPos, long newTierIndex, long newPos,
long timestamp) {
for (long next = modIterSet.nextSetBit(0L); next > 0L;
next = modIterSet.nextSetBit(next + 1L)) {
try {
ModificationIterator modificationIterator =
acquireModificationIterator((byte) next);
if (modificationIterator.dropChange(oldTierIndex, oldPos))
modificationIterator.raiseChange(newTierIndex, newPos, timestamp);
} catch (Exception e) {
LOG.error("", e);
public void moveChange(long oldTierIndex, long oldPos, long newTierIndex, long newPos) {
if (oldTierIndex <= actualSegments) {
long oldSegmentIndex = oldTierIndex - 1;
long oldOffsetToTierBitSet = oldSegmentIndex * tierModIterBitSetOuterSize;
if (newTierIndex <= actualSegments) {
long newSegmentIndex = newTierIndex - 1;
long newOffsetToTierBitSet = newSegmentIndex * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator.dropChangeInSegment(oldOffsetToTierBitSet, oldPos))
modificationIterator.raiseChangeInSegment(newOffsetToTierBitSet, newPos);
}
} else {
long newExtraTierIndex = newTierIndex - 1 - actualSegments;
int newBulkIndex = (int) (newExtraTierIndex >> log2TiersInBulk);
long newOffsetToTierBitSet =
(newExtraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator.dropChangeInSegment(oldOffsetToTierBitSet, oldPos)) {
modificationIterator
.raiseChangeInTierBulk(newBulkIndex, newOffsetToTierBitSet, newPos);
}
}
}
} else {
long oldExtraTierIndex = oldTierIndex - 1 - actualSegments;
int oldBulkIndex = (int) (oldExtraTierIndex >> log2TiersInBulk);
long oldOffsetToTierBitSet =
(oldExtraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
if (newTierIndex <= actualSegments) {
long newSegmentIndex = newTierIndex - 1;
long newOffsetToTierBitSet = newSegmentIndex * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator
.dropChangeInTierBulk(oldBulkIndex, oldOffsetToTierBitSet, oldPos)) {
modificationIterator.raiseChangeInSegment(newOffsetToTierBitSet, newPos);
}
}
} else {
long newExtraTierIndex = newTierIndex - 1 - actualSegments;
int newBulkIndex = (int) (newExtraTierIndex >> log2TiersInBulk);
long newOffsetToTierBitSet =
(newExtraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator
.dropChangeInTierBulk(oldBulkIndex, oldOffsetToTierBitSet, oldPos)) {
modificationIterator
.raiseChangeInTierBulk(newBulkIndex, newOffsetToTierBitSet, newPos);
}
}
}
}
}
public boolean isChanged(long tierIndex, long pos) {
for (long next = modIterSet.nextSetBit(0L); next > 0L;
next = modIterSet.nextSetBit(next + 1L)) {
ModificationIterator modificationIterator =
acquireModificationIterator((byte) next);
if (modificationIterator.isChanged(tierIndex, pos))
return true;
if (tierIndex <= actualSegments) {
long segmentIndex = tierIndex - 1;
long offsetToTierBitSet = segmentIndex * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator.isChangedSegment(offsetToTierBitSet, pos))
return true;
}
} else {
long extraTierIndex = tierIndex - 1 - actualSegments;
int bulkIndex = (int) (extraTierIndex >> log2TiersInBulk);
long offsetToTierBitSet =
(extraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
for (ModificationIterator modificationIterator : assignedModificationIterators) {
if (modificationIterator.isChangedTierBulk(bulkIndex, offsetToTierBitSet, pos))
return true;
}
}
return false;
}
@@ -362,17 +448,27 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
return entry.originIdentifier() == identifier();
}
@Override
public void writeExternalEntry(
ReplicableEntry entry, Bytes payload, @NotNull Bytes destination, int chronicleId) {
if (payload != null)
writePayload(payload, destination);
if (entry != null)
writeExternalEntry0(entry, destination);
}
private void writePayload(Bytes payload, Bytes destination) {
destination.writeByte(BOOTSTRAP_TIME_HUNK);
destination.write(payload, payload.readPosition(), payload.readRemaining());
}
/**
* This method does not set a segment lock, A segment lock should be obtained before calling
* this method, especially when being used in a multi threaded context.
*/
@Override
public void writeExternalEntry(
ReplicableEntry entry, Bytes payload, @NotNull Bytes destination,
int chronicleId, long bootstrapTime) {
private void writeExternalEntry0(ReplicableEntry entry, Bytes destination) {
destination.writeByte(ENTRY_HUNK);
destination.writeLong(bootstrapTime);
destination.writeStopBit(entry.originTimestamp());
if (entry.originIdentifier() == 0)
throw new IllegalStateException("Identifier can't be 0");
@@ -432,6 +528,7 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
@Override
public CompiledReplicatedMapQueryContext<K, V, R> mapContext() {
//noinspection unchecked
return q().getContext(CompiledReplicatedMapQueryContext.class,
ci -> new CompiledReplicatedMapQueryContext<>(ci, this));
}
@@ -441,9 +538,15 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
* this method, especially when being used in a multi threaded context.
*/
@Override
public void readExternalEntry(@NotNull Bytes source) {
try (CompiledReplicatedMapQueryContext<K, V, R> remoteOpContext = mapContext()) {
remoteOpContext.processReplicatedEvent(source);
public void readExternalEntry(@NotNull Bytes source, byte remoteNodeIdentifier) {
byte hunk = source.readByte();
if (hunk == BOOTSTRAP_TIME_HUNK) {
setRemoteNodeCouldBootstrapFrom(remoteNodeIdentifier, source.readLong());
} else {
assert hunk == ENTRY_HUNK;
try (CompiledReplicatedMapQueryContext<K, V, R> remoteOpContext = mapContext()) {
remoteOpContext.processReplicatedEvent(source);
}
}
}
@@ -458,6 +561,7 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
}
public CompiledReplicatedMapIterationContext<K, V, R> iterationContext() {
//noinspection unchecked
return i().getContext(CompiledReplicatedMapIterationContext.class,
ci -> new CompiledReplicatedMapIterationContext<>(ci, this));
}
@@ -476,134 +580,109 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
* @author Rob Austin.
*/
class ModificationIterator implements Replica.ModificationIterator {
private final byte remoteIdentifier;
private final long segmentBitSetsAddr;
private final long offsetToBitSetsWithinATierBulk;
private ModificationNotifier modificationNotifier;
private final long mainSegmentsChangesBitSetAddr;
private final int segmentIndexShift;
private final long posMask;
/**
* Holds a single long value. Is kept as Bytes, to reuse existing "payload write" option in
* {@link net.openhft.chronicle.map.Replica.EntryCallback}
*/
private final Bytes bootstrapTimeAfterNextReplicationIterationComplete =
Bytes.wrapForRead(new byte[8]);
private boolean somethingSentOnThisIteration = false;
private final long offsetToBitSetWithinATierBulk;
// The iteration "cursor" consists of 4 fields:
// 1) if segmentIndex >= 0, bulkIndex = -1, tierIndexOffsetWithinBulk = -1:
// => we are in "first tiers" aka "segments"
// 2) segmentIndex = -1, bulkIndex >= 0, tierIndexOffsetWithinBulk >= 0:
// => we are in extra tiers
private int segmentIndex;
private int bulkIndex;
private int tierIndexOffsetWithinBulk;
/**
* Corresponds to {@link net.openhft.chronicle.hash.impl.stage.entry.HashEntryStages#pos}
*/
private long entryPos;
// when a bootstrap is send this is the time stamp that the client will bootstrap up to
// if it is set as ZERO then the onPut() will set it to the current time, once the
// consumer has cycled through the bit set the timestamp will be set back to zero.
private AtomicLong bootStrapTimeStamp = new AtomicLong();
private long lastBootStrapTimeStamp = currentTime();
/**
* Cached addr of the changes bit set of the current tier (which "cursor" points to),
* to avoid re-computation of this addr in {@link #entryIsStillDirty(long)} and
* {@link #clearEntry(long)} methods
*/
private long tierBitSetAddr;
// records the current position of the cursor in the bitset
// TODO volatile?
private volatile long position = NOT_FOUND;
public ModificationIterator(byte remoteIdentifier) {
this.remoteIdentifier = remoteIdentifier;
segmentBitSetsAddr = bsAddress() + startOfModificationIterators +
remoteIdentifier * segmentModIterBitSetsForIdentifierOuterSize;
nativeAccess().zeroOut(null, segmentBitSetsAddr,
segmentModIterBitSetsForIdentifierOuterSize);
offsetToBitSetsWithinATierBulk =
remoteIdentifier * tierBulkModIterBitSetsForIdentifierOuterSize;
// -1 for main segments area, 0-N is tier bulk id
private int iterationMainSegmentsAreaOrTierBulk = -1;
private long tierBulkBitSetAddr = 0L;
public ModificationIterator(int remoteIdentifier) {
long bitsPerSegment = bitsPerSegmentInModIterBitSet();
segmentIndexShift = Long.numberOfTrailingZeros(bitsPerSegment);
posMask = bitsPerSegment - 1L;
mainSegmentsChangesBitSetAddr = bsAddress() + startOfModificationIterators +
remoteIdentifier * modIterBitSetSizeInBytes();
nativeAccess().zeroOut(null, mainSegmentsChangesBitSetAddr, modIterBitSetSizeInBytes());
offsetToBitSetWithinATierBulk =
remoteIdentifier * tierBulkModIterBitSetSizeInBytes(tiersInBulk);
resetCursor();
}
public ModificationIterator(int remoteIdentifier, ModificationNotifier notifier) {
this(remoteIdentifier);
setModificationNotifier(notifier);
private void resetCursor() {
segmentIndex = 0;
bulkIndex = -1;
tierIndexOffsetWithinBulk = -1;
entryPos = -1;
tierBitSetAddr = segmentBitSetsAddr; // + tierModIterBitSetOuterSize * segmentIndex = 0
}
private void setBootstrapTimeAfterNextReplicationIterationComplete(long bootstrapTime) {
bootstrapTimeAfterNextReplicationIterationComplete.writeLong(0, bootstrapTime);
}
public void setModificationNotifier(@NotNull ModificationNotifier modificationNotifier) {
this.modificationNotifier = modificationNotifier;
}
/**
* used to merge multiple segments and positions into a single index used by the bit map
*
* @param tierIndex the index of the maps segment
* @param pos the position within this {@code tierIndex}
* @return and index the has combined the {@code tierIndex} and {@code pos} into a
* single value
*/
private long combine(long tierIndex, long pos) {
long tierIndexMinusOne = tierIndex - 1;
if (tierIndexMinusOne < actualSegments)
return (tierIndexMinusOne << segmentIndexShift) | pos;
return combineExtraTier(tierIndexMinusOne, pos);
}
private long combineExtraTier(long tierIndexMinusOne, long pos) {
long extraTierIndex = tierIndexMinusOne - actualSegments;
long tierIndexOffsetWithinBulk = extraTierIndex & (tiersInBulk - 1);
return (tierIndexOffsetWithinBulk << segmentIndexShift) | pos;
}
void raiseChange(long tierIndex, long pos, long timestamp) {
LOG.debug("raise change: id {}, tierIndex {}, pos {}",
localIdentifier, tierIndex, pos);
long bitIndex = combine(tierIndex, pos);
if (tierIndex <= actualSegments) {
mainSegmentsModIterFrameForUpdates.set(nativeAccess(), null,
mainSegmentsChangesBitSetAddr, bitIndex);
} else {
raiseExtraTierChange(tierIndex, bitIndex);
}
// todo improve this - use the timestamp from the entry its self
bootStrapTimeStamp.compareAndSet(0, timestamp);
void raiseChangeInSegment(long offsetToTierBitSet, long pos) {
tierModIterFrame.set(nativeAccess(), null,
segmentBitSetsAddr + offsetToTierBitSet, pos);
if (modificationNotifier != null)
modificationNotifier.onChange();
}
private void raiseExtraTierChange(long tierIndex, long bitIndex) {
tierIndex = tierIndex - actualSegments - 1;
long bulkIndex = tierIndex >> log2TiersInBulk;
TierBulkData tierBulkData = tierBulkOffsets.get((int) bulkIndex);
long bitSetAddr = bitSetAddr(tierBulkData);
tierBulkModIterFrameForUpdates.set(nativeAccess(), null, bitSetAddr, bitIndex);
void raiseChangeInTierBulk(int bulkIndex, long offsetToTierBitSet, long pos) {
TierBulkData tierBulkData = tierBulkOffsets.get(bulkIndex);
long bitSetAddr = bitSetsAddr(tierBulkData) + offsetToTierBitSet;
tierModIterFrame.set(nativeAccess(), null, bitSetAddr, pos);
if (modificationNotifier != null)
modificationNotifier.onChange();
}
boolean dropChange(long tierIndex, long pos) {
LOG.debug("drop change: id {}, tierIndex {}, pos {}", localIdentifier, tierIndex, pos);
long bitIndex = combine(tierIndex, pos);
if (tierIndex <= actualSegments) {
return mainSegmentsModIterFrameForUpdates.clearIfSet(nativeAccess(), null,
mainSegmentsChangesBitSetAddr, bitIndex);
} else {
return dropExtraTierChange(tierIndex, bitIndex);
}
boolean dropChangeInSegment(long offsetToTierBitSet, long pos) {
return tierModIterFrame.clearIfSet(nativeAccess(), null,
segmentBitSetsAddr + offsetToTierBitSet, pos);
}
private boolean dropExtraTierChange(long tierIndex, long bitIndex) {
tierIndex = tierIndex - actualSegments - 1;
long bulkIndex = tierIndex >> log2TiersInBulk;
TierBulkData tierBulkData = tierBulkOffsets.get((int) bulkIndex);
long bitSetAddr = bitSetAddr(tierBulkData);
return tierBulkModIterFrameForUpdates.clearIfSet(nativeAccess(), null,
bitSetAddr, bitIndex);
boolean dropChangeInTierBulk(int bulkIndex, long offsetToTierBitSet, long pos) {
TierBulkData tierBulkData = tierBulkOffsets.get(bulkIndex);
long bitSetAddr = bitSetsAddr(tierBulkData) + offsetToTierBitSet;
return tierModIterFrame.clearIfSet(nativeAccess(), null, bitSetAddr, pos);
}
private long bitSetAddr(TierBulkData tierBulkData) {
boolean isChangedSegment(long offsetToTierBitSet, long pos) {
return tierModIterFrame.isSet(nativeAccess(), null,
segmentBitSetsAddr + offsetToTierBitSet, pos);
}
boolean isChangedTierBulk(int bulkIndex, long offsetToTierBitSet, long pos) {
TierBulkData tierBulkData = tierBulkOffsets.get(bulkIndex);
long bitSetAddr = bitSetsAddr(tierBulkData) + offsetToTierBitSet;
return tierModIterFrame.isSet(nativeAccess(), null, bitSetAddr, pos);
}
private long bitSetsAddr(TierBulkData tierBulkData) {
return tierBulkData.bytesStore.address(tierBulkData.offset) +
offsetToBitSetWithinATierBulk;
}
boolean isChanged(long tierIndex, long pos) {
long bitIndex = combine(tierIndex, pos);
if (tierIndex <= actualSegments) {
return mainSegmentsModIterFrameForUpdates.isSet(nativeAccess(), null,
mainSegmentsChangesBitSetAddr, bitIndex);
} else {
return isChangedExtraTier(tierIndex, bitIndex);
}
}
private boolean isChangedExtraTier(long tierIndex, long bitIndex) {
long extraTierIndex = tierIndex - actualSegments - 1;
long bulkIndex = extraTierIndex >> log2TiersInBulk;
TierBulkData tierBulkData = tierBulkOffsets.get((int) bulkIndex);
long bitSetAddr = bitSetAddr(tierBulkData);
return tierBulkModIterFrameForUpdates.isSet(nativeAccess(), null, bitSetAddr, bitIndex);
offsetToBitSetsWithinATierBulk;
}
/**
@@ -615,47 +694,92 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
*/
@Override
public boolean hasNext() {
return nextPosition() >= 0;
return nextEntryPos(null, 0) != NOT_FOUND;
}
private long nextPosition() {
long nextPos;
long position = this.position;
private long nextEntryPos(EntryCallback entryCallback, int chronicleId) {
long nextEntryPos;
boolean allBitSetsScannedFromTheStart = false;
// at most 2 iterations
while (!allBitSetsScannedFromTheStart) {
if (iterationMainSegmentsAreaOrTierBulk == -1) {
allBitSetsScannedFromTheStart = position < 0;
if ((nextPos = mainSegmentsModIterFrameForIteration.nextSetBit(nativeAccess(),
null, mainSegmentsChangesBitSetAddr, position + 1)) != NOT_FOUND) {
return nextPos;
} else {
this.position = position = NOT_FOUND;
// go to the first bulk
iterationMainSegmentsAreaOrTierBulk = 0;
if (segmentIndex >= 0) {
allBitSetsScannedFromTheStart = segmentIndex == 0 && entryPos == -1;
if (allBitSetsScannedFromTheStart) {
setBootstrapTimeAfterNextReplicationIterationComplete(currentTime());
somethingSentOnThisIteration = false;
}
while (segmentIndex < actualSegments) {
// This is needed to ensure, that any entry update with the timestamp,
// smaller than assigned for
// bootstrapTimeAfterNextReplicationIterationComplete, is visible during
// the iteration of the current segment. Bits are raised during the update
// via non-volatile bit set (performance concerns), hence to guarantee
// visibility during the iteration, we build a happens-before between bit
// raise and bit reading:
// bit raised ->
// lock released (end of update operation) ->
// lock acquired (the following acquireAndReleaseReadLock() call) ->
// bits are iterated (raised bits should be visible here)
if (entryPos == -1) {
acquireAndReleaseReadLock(segmentIndex);
}
if ((nextEntryPos = tierModIterFrame.nextSetBit(nativeAccess(),
null, tierBitSetAddr, entryPos + 1)) != NOT_FOUND) {
return nextEntryPos;
} else {
segmentIndex++;
tierBitSetAddr += tierModIterBitSetOuterSize;
entryPos = -1;
}
}
// go to extra bulks
segmentIndex = -1;
bulkIndex = 0;
tierIndexOffsetWithinBulk = 0;
if (bulkIndex < globalMutableState().getAllocatedExtraTierBulks())
tierBitSetAddr = bitSetsAddr(tierBulkOffsets.get(bulkIndex));
}
// for each allocated tier bulk
while (iterationMainSegmentsAreaOrTierBulk <
globalMutableState().getAllocatedExtraTierBulks()) {
VanillaChronicleHash.TierBulkData tierBulkData =
tierBulkOffsets.get(iterationMainSegmentsAreaOrTierBulk);
tierBulkBitSetAddr = bitSetAddr(tierBulkData);
if ((nextPos = tierBulkModIterFrameForIteration.nextSetBit(nativeAccess(), null,
tierBulkBitSetAddr, position + 1)) != NOT_FOUND) {
return nextPos;
while (bulkIndex < globalMutableState().getAllocatedExtraTierBulks()) {
while (tierIndexOffsetWithinBulk < tiersInBulk) {
if ((nextEntryPos = tierModIterFrame.nextSetBit(nativeAccess(), null,
tierBitSetAddr, entryPos + 1)) != NOT_FOUND) {
return nextEntryPos;
} else {
tierIndexOffsetWithinBulk++;
tierBitSetAddr += tierModIterBitSetOuterSize;
entryPos = -1;
}
}
// go to the next bulk
iterationMainSegmentsAreaOrTierBulk++;
this.position = position = NOT_FOUND;
bulkIndex++;
tierIndexOffsetWithinBulk = 0;
if (bulkIndex < globalMutableState().getAllocatedExtraTierBulks())
tierBitSetAddr = bitSetsAddr(tierBulkOffsets.get(bulkIndex));
}
resetCursor();
// we walked through the whole chronicle map instance, "replication iteration"
if (entryCallback != null && somethingSentOnThisIteration) {
// send the previous bootstrap time
entryCallback.onEntry(
null, bootstrapTimeAfterNextReplicationIterationComplete, chronicleId);
}
this.iterationMainSegmentsAreaOrTierBulk = -1;
this.position = position = NOT_FOUND;
bootStrapTimeStamp.set(0);
}
return NOT_FOUND;
}
private void acquireAndReleaseReadLock(int segmentIndex) {
try (CompiledReplicatedMapIterationContext<K, V, R> c =
iterationContext()) {
c.initSegmentIndex(segmentIndex);
c.readLock().lock();
}
}
/**
* @param entryCallback call this to get an entry, this class will take care of the locking
* @return true if an entry was processed
@@ -663,95 +787,51 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
@Override
public boolean nextEntry(@NotNull EntryCallback entryCallback, int chronicleId) {
while (true) {
long position = nextPosition();
if (position == NOT_FOUND) {
this.position = NOT_FOUND;
long nextEntryPos = nextEntryPos(entryCallback, chronicleId);
if (nextEntryPos == NOT_FOUND)
return false;
}
entryPos = nextEntryPos;
this.position = position;
int segmentIndexOrTierIndexOffsetWithinBulk =
(int) (position >>> segmentIndexShift);
try (CompiledReplicatedMapIterationContext<K, V, R> context =
iterationContext()) {
if (iterationMainSegmentsAreaOrTierBulk < 0) {
// if main area
// segmentIndexOrTierIndexOffsetWithinBulk is segment index
context.initSegmentIndex(segmentIndexOrTierIndexOffsetWithinBulk);
try (CompiledReplicatedMapIterationContext<K, V, R> context = iterationContext()) {
if (segmentIndex >= 0) {
// we are in first tiers (aka "segments")
context.initSegmentIndex(segmentIndex);
} else {
// extra tiers
// segmentIndexOrTierIndexOffsetWithinBulk is tier index offset within bulk
TierBulkData tierBulkData =
tierBulkOffsets.get(iterationMainSegmentsAreaOrTierBulk);
long tierBaseAddr = tierAddr(tierBulkData,
segmentIndexOrTierIndexOffsetWithinBulk);
// we are in extra tiers
TierBulkData tierBulkData = tierBulkOffsets.get(bulkIndex);
long tierBaseAddr = tierAddr(tierBulkData, tierIndexOffsetWithinBulk);
long tierCountersAreaAddr = tierBaseAddr + tierHashLookupOuterSize;
context.initSegmentIndex(
TierCountersArea.segmentIndex(tierCountersAreaAddr));
int tier = TierCountersArea.tier(tierCountersAreaAddr);
long tierIndex = actualSegments +
(iterationMainSegmentsAreaOrTierBulk << log2TiersInBulk) +
segmentIndexOrTierIndexOffsetWithinBulk + 1;
(bulkIndex << log2TiersInBulk) + tierIndexOffsetWithinBulk + 1;
context.initSegmentTier(tier, tierIndex, tierBaseAddr);
}
context.updateLock().lock();
if (changesForUpdatesGet(position)) {
final long segmentPos = position & posMask;
context.readExistingEntry(segmentPos);
// it may not be successful if the buffer can not be re-sized so we will
// process it later, by NOT clearing the changes.clear(position)
boolean success = entryCallback.onEntry(
(ReplicableEntry) context.entryForIteration(), null,
chronicleId, bootStrapTimeStamp());
if (success)
changesForUpdatesClear(position);
return success;
if (entryIsStillDirty(entryPos)) {
context.readExistingEntry(entryPos);
entryCallback.onEntry(
(ReplicableEntry) context.entryForIteration(), null, chronicleId);
somethingSentOnThisIteration = true;
clearEntry(entryPos);
return true;
}
// if the position was already cleared by another thread
// if the entryPos was already cleared by another thread
// while we were trying to obtain segment lock (for example, in relocation()),
// go to pick up next (next iteration in while (true) loop)
// go to pick up next (next iteration in the `while (true)` loop)
}
}
}
private boolean changesForUpdatesGet(long position) {
if (iterationMainSegmentsAreaOrTierBulk < 0) {
return mainSegmentsModIterFrameForUpdates.get(nativeAccess(), null,
mainSegmentsChangesBitSetAddr, position);
} else {
return tierBulkModIterFrameForUpdates.get(nativeAccess(), null,
tierBulkBitSetAddr, position);
}
private boolean entryIsStillDirty(long entryPos) {
return tierModIterFrame.get(nativeAccess(), null, tierBitSetAddr, entryPos);
}
private void changesForUpdatesClear(long position) {
if (iterationMainSegmentsAreaOrTierBulk < 0) {
mainSegmentsModIterFrameForUpdates.clear(nativeAccess(), null,
mainSegmentsChangesBitSetAddr, position);
} else {
tierBulkModIterFrameForUpdates.clear(nativeAccess(), null,
tierBulkBitSetAddr, position);
}
}
/**
* @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.
*/
private long bootStrapTimeStamp() {
final long timeStamp = bootStrapTimeStamp.get();
long result = (timeStamp == 0) ? this.lastBootStrapTimeStamp : timeStamp;
this.lastBootStrapTimeStamp = result;
return result;
private void clearEntry(long entryPos) {
tierModIterFrame.clear(nativeAccess(), null, tierBitSetAddr, entryPos);
}
@Override
@@ -775,14 +855,39 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
fromTimeStamp, e.originTimestamp(),
e.originIdentifier(), localIdentifier);
}
if (e.originTimestamp() >= fromTimeStamp &&
(!bootstrapOnlyLocalEntries ||
e.originIdentifier() == localIdentifier)) {
raiseChange(c.tierIndex(), c.pos(), c.timestamp());
if (bootstrapOnlyLocalEntries) {
if (e.originIdentifier() == localIdentifier &&
e.originTimestamp() >= fromTimeStamp) {
raiseChangeDuringBootstrap(c);
}
} else {
// TODO currently, all entries, originating not from the current node,
// are bootstrapped. This could be optimized, but requires to generate
// unique connection id, it identify two ChronicleMap instances
// reconnecting vs. different Map start-up
if (e.originIdentifier() != localIdentifier ||
e.originTimestamp() >= fromTimeStamp) {
raiseChangeDuringBootstrap(c);
}
}
});
}
}
}
private void raiseChangeDuringBootstrap(CompiledReplicatedMapIterationContext<K, V, R> c) {
long tierIndex = c.tierIndex();
if (tierIndex <= actualSegments) {
long segmentIndex = tierIndex - 1;
long offsetToTierBitSet = segmentIndex * tierModIterBitSetOuterSize;
raiseChangeInSegment(offsetToTierBitSet, c.pos());
} else {
long extraTierIndex = tierIndex - 1 - actualSegments;
int bulkIndex = (int) (extraTierIndex >> log2TiersInBulk);
long offsetToTierBitSet =
(extraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
raiseChangeInTierBulk(bulkIndex, offsetToTierBitSet, c.pos());
}
}
}
}
@@ -652,7 +652,8 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable
attached.remoteModificationIterator.setModificationNotifier(attached);
writer.writeRemoteBootstrapTimestamp(replica.lastModificationTime(remoteIdentifier));
writer.writeRemoteBootstrapTimestamp(
replica.remoteNodeCouldBootstrapFrom(remoteIdentifier));
writer.writeServerVersion();
@@ -1253,7 +1254,6 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable
*/
class TcpSocketChannelEntryReader {
public static final int HEADROOM = 1024;
public static final int SIZE_OF_BOOTSTRAP_TIMESTAMP = 8;
public long lastHeartBeatReceived = System.currentTimeMillis();
ByteBuffer socketIn;
Bytes entryOut;
@@ -1332,8 +1332,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable
// if the buffer is too small to read this payload we will have to grow the
// size of the buffer
long requiredSize = sizeInBytes + SIZE_OF_SIZE + 1 +
SIZE_OF_BOOTSTRAP_TIMESTAMP;
long requiredSize = sizeInBytes + SIZE_OF_SIZE + 1;
if (entryOut.capacity() < requiredSize) {
attached.entryReader.resizeBuffer(requiredSize + HEADROOM);
}
@@ -1354,7 +1353,7 @@ final class TcpReplicator extends AbstractChannelReplicator implements Closeable
final long limit = entryOut.readLimit();
entryOut.readLimit(nextEntryPos);
externalizable.readExternalEntry(entryOut);
externalizable.readExternalEntry(entryOut, attached.remoteIdentifier);
entryOut.readLimit(limit);
@@ -41,7 +41,8 @@ import static java.nio.channels.SelectionKey.OP_WRITE;
* receive the data before it becomes available on TCP/IP. In order to not miss data, UdpReplicator
* should be used in conjunction with the TCP Replicator.
*/
class UdpChannelReplicator extends AbstractChannelReplicator implements Replica.ModificationNotifier, Closeable {
class UdpChannelReplicator extends AbstractChannelReplicator
implements Replica.ModificationNotifier, Closeable {
private static final Logger LOG =
LoggerFactory.getLogger(UdpChannelReplicator.class.getName());
@@ -27,6 +27,8 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import static net.openhft.chronicle.map.ChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID;
/**
* The UdpReplicator attempts to read the data ( but it does not enforce or grantee delivery ),
* typically, you should use the UdpReplicator if you have a large number of nodes, and you wish to
@@ -48,7 +50,7 @@ final class UdpReplicator extends UdpChannelReplicator
super(replicationConfig, replica.identifier());
Replica.ModificationIterator modificationIterator = replica.acquireModificationIterator(
ChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID);
UDP_REPLICATION_MODIFICATION_ITERATOR_ID);
modificationIterator.setModificationNotifier(this);
setReader(new UdpSocketChannelEntryReader(replicationConfig.udpBufferSize(), entryExternalizable));
@@ -111,7 +113,7 @@ final class UdpReplicator extends UdpChannelReplicator
if (entryOut.readRemaining() != size)
return;
externalizable.readExternalEntry(entryOut);
externalizable.readExternalEntry(entryOut, UDP_REPLICATION_MODIFICATION_ITERATOR_ID);
}
}
@@ -49,8 +49,6 @@ public abstract class ReplicatedInput<K, V, R> implements RemoteOperationContext
}
public void processReplicatedEvent(Bytes replicatedInputBytes) {
long bootstrapTimestamp = replicatedInputBytes.readLong();
long timestamp = replicatedInputBytes.readStopBit();
byte identifier = replicatedInputBytes.readByte();
ru.initReplicationUpdate(identifier, timestamp);
@@ -59,8 +57,6 @@ public abstract class ReplicatedInput<K, V, R> implements RemoteOperationContext
long keySize = mh.m().keySizeMarshaller.readSize(replicatedInputBytes);
long keyOffset = replicatedInputBytes.readPosition();
mh.m().setLastModificationTime(identifier, bootstrapTimestamp);
q.initInputKey(q.getInputKeyBytesAsData(replicatedInputBytes, keyOffset, keySize));
if (isDeleted) {
s.innerUpdateLock.lock();
@@ -79,7 +75,6 @@ public abstract class ReplicatedInput<K, V, R> implements RemoteOperationContext
@Override
public void remotePut(Data<V> newValue, byte remoteIdentifier, long timestamp) {
ru.initReplicationUpdate(remoteIdentifier, timestamp);
mh.m().setLastModificationTime(remoteIdentifier, timestamp);
s.innerUpdateLock.lock();
mh.m().remoteOperations.put(this, newValue);
}
@@ -87,7 +82,6 @@ public abstract class ReplicatedInput<K, V, R> implements RemoteOperationContext
@Override
public void remoteRemove(byte remoteIdentifier, long timestamp) {
ru.initReplicationUpdate(remoteIdentifier, timestamp);
mh.m().setLastModificationTime(remoteIdentifier, timestamp);
s.innerWriteLock.lock();
mh.m().remoteOperations.remove(this);
}
@@ -49,7 +49,7 @@ public abstract class ReplicationUpdate<K> implements RemoteOperationContext<K>
}
public void moveChange(long oldTierIndex, long oldPos, long newPos) {
mh.m().moveChange(oldTierIndex, oldPos, s.tierIndex, newPos, e.timestamp());
mh.m().moveChange(oldTierIndex, oldPos, s.tierIndex, newPos);
}
public void updateChange() {
@@ -59,7 +59,7 @@ public abstract class ReplicationUpdate<K> implements RemoteOperationContext<K>
}
public void raiseChange() {
mh.m().raiseChange(s.tierIndex, e.pos, e.timestamp());
mh.m().raiseChange(s.tierIndex, e.pos);
}
public boolean changed() {
@@ -17,6 +17,7 @@
package net.openhft.chronicle.map;
import net.openhft.chronicle.algo.MemoryUnit;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
@@ -32,6 +33,7 @@ public class MarkTest {
static int ENTRIES = 25_000_000;
@Ignore("often out of time, that is a parf issue, not a bug")
@Test(timeout = 25000)
public void inMemoryTest() {
test(ChronicleMapBuilder::create);
@@ -18,6 +18,7 @@ package net.openhft.chronicle.map;
import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
@@ -30,6 +31,7 @@ import java.util.concurrent.TimeUnit;
*/
public class ReplicateProxyMapUpdatedViaSharedMemoryTest {
@Ignore("HCOLL-417")
@Test(timeout = 10000)
public void testReplicateProxyMapUpdatedViaSharedMemory() throws IOException, InterruptedException {
@@ -59,13 +59,13 @@ public class TCPSocketReplicationBootStrapTest {
map2aBuilder.createPersistedTo(persistenceFile);
map2a.put(10, "EXAMPLE-10"); // this will be the last time that map1 go an update from map2
long lastModificationTime;
long bootstrapFrom;
// lets make sure that the message has got to map 1
do {
lastModificationTime = map1.lastModificationTime((byte) 2);
bootstrapFrom = map1.remoteNodeCouldBootstrapFrom((byte) 2);
Thread.yield();
} while (lastModificationTime == 0);
} while (bootstrapFrom == 0);
final File map2File = map2a.file();
map2a.close();
@@ -126,13 +126,13 @@ public class TCPSocketReplicationBootStrapTest {
map2a.put(10, "EXAMPLE-10"); // this will be the last time that map1 go an update from map2
long lastModificationTime;
long bootstrapFrom;
// lets make sure that the message has got to map 1
do {
lastModificationTime = map1.lastModificationTime((byte) 2);
bootstrapFrom = map1.remoteNodeCouldBootstrapFrom((byte) 2);
Thread.yield();
} while (lastModificationTime == 0);
} while (bootstrapFrom == 0);
map2a.close();