diff --git a/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java b/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java index a0b9f6261..926401cfe 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/TcpTransportAndNetworkConfig.java @@ -134,7 +134,7 @@ public final class TcpTransportAndNetworkConfig implements Serializable { heartBeatIntervalUnit); } - public int packetSize() { + public int tcpBufferSize() { return tcpBufferSize; } diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java index 8f87caed2..390337a06 100644 --- a/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/AbstractChannelReplicator.java @@ -32,6 +32,8 @@ import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.channels.*; import java.security.AccessController; import java.security.PrivilegedAction; @@ -76,8 +78,7 @@ abstract class AbstractChannelReplicator implements Closeable { ThreadLocalCopies copies; VanillaChronicleMap.SegmentState segmentState; - AbstractChannelReplicator(String name, ThrottlingConfig throttlingConfig, - int maxEntrySizeBytes) + AbstractChannelReplicator(String name, ThrottlingConfig throttlingConfig) throws IOException { executorService = Executors.newSingleThreadExecutor( new NamedThreadFactory(name, true) { @@ -90,7 +91,7 @@ abstract class AbstractChannelReplicator implements Closeable { throttler = throttlingConfig.throttling(DAYS) > 0 ? new Throttler(selector, throttlingConfig.bucketInterval(MILLISECONDS), - maxEntrySizeBytes, throttlingConfig.throttling(DAYS)) : null; + throttlingConfig.throttling(DAYS)) : null; startedHere = new Throwable("Started here"); } @@ -317,12 +318,11 @@ abstract class AbstractChannelReplicator implements Closeable { Throttler(@NotNull Selector selector, long throttleIntervalInMillis, - long serializedEntrySize, long bitsPerDay) { this.selector = selector; this.throttleInterval = throttleIntervalInMillis; double bytesPerMs = ((double) bitsPerDay) / DAYS.toMillis(1) / BITS_IN_A_BYTE; - this.maxBytesInInterval = round(bytesPerMs * throttleInterval) - serializedEntrySize; + this.maxBytesInInterval = round(bytesPerMs * throttleInterval); } public void add(SelectableChannel selectableChannel) { @@ -413,16 +413,74 @@ abstract class AbstractChannelReplicator implements Closeable { static class EntryCallback extends Replica.EntryCallback { private final Replica.EntryExternalizable externalizable; - private final ByteBufferBytes in; + + @NotNull + private ByteBufferBytes in; + + @NotNull + private ByteBuffer out; EntryCallback(@NotNull final Replica.EntryExternalizable externalizable, - @NotNull final ByteBufferBytes in) { + final int tcpBufferSize) { this.externalizable = externalizable; - this.in = in; + out = ByteBuffer.allocateDirect(tcpBufferSize); + in = new ByteBufferBytes(out); } + @NotNull + public ByteBufferBytes in() { + return in; + } + + @NotNull + public ByteBuffer out() { + return out; + } + + + private void resizeBuffer(int size) { + + if (LOG.isDebugEnabled()) + LOG.debug("resizing buffer to size=" + size); + + if (size < out.capacity()) + throw new IllegalStateException("it not possible to resize the buffer smaller"); + + assert size < Integer.MAX_VALUE; + + final ByteBuffer result = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); + final long bytesPosition = in.position(); + + in = new ByteBufferBytes(result.slice()); + + out.position(0); + out.limit((int) bytesPosition); + + int numberOfLongs = (int) bytesPosition / 8; + + // chunk in longs first + for (int i = 0; i < numberOfLongs; i++) { + in.writeLong(out.getLong()); + } + + for (int i = numberOfLongs * 8; i < bytesPosition; i++) { + in.writeByte(out.get()); + } + + out = result; + + assert out.capacity() == in.capacity(); + + assert out.capacity() == size; + assert out.capacity() == in.capacity(); + assert in.limit() == in.capacity(); + + } + + @Override public boolean onEntry(final Bytes entry, final int chronicleId) { + long pos0 = in.position(); // used to denote that this is not a stateless map event @@ -433,7 +491,16 @@ abstract class AbstractChannelReplicator implements Closeable { // this is where we will store the size of the entry in.skip(SIZE_OF_SIZE); + int entrySize = externalizable.sizeOfEntry(entry, chronicleId); + + if (entrySize > in.remaining()) { + long newSize = in.position() + entrySize; + assert newSize < Integer.MAX_VALUE; + resizeBuffer((int) newSize); + } + final long start = in.position(); + externalizable.writeExternalEntry(entry, in, chronicleId); if (in.position() == start) { @@ -443,16 +510,16 @@ abstract class AbstractChannelReplicator implements Closeable { // 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 to read - final long entrySize = (int) (in.position() - start); - if (entrySize > Integer.MAX_VALUE) + final long bytesWritten = (int) (in.position() - start); + + if (bytesWritten > Integer.MAX_VALUE) throw new IllegalStateException("entry too large, the entry size=" + entrySize + ", " + "entries are limited to a size of " + Integer.MAX_VALUE); - int entrySize1 = (int) entrySize; if (LOG.isDebugEnabled()) - LOG.debug("sending entry of entrySize=" + entrySize1); + LOG.debug("sending entry of entrySize=" + (int) bytesWritten); - in.writeInt(sizeLocation, entrySize1); + in.writeInt(sizeLocation, (int) bytesWritten); return true; } diff --git a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java index bedc095cb..addfb502e 100644 --- a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java @@ -64,7 +64,7 @@ final class ChannelProvider implements Closeable { final TcpReplicator tcpReplicator = new TcpReplicator( channelProvider.asReplica, channelProvider.asEntryExternalizable, - tcpConfig, hub.maxEntrySize(), null, hub.remoteNodeValidator()); + tcpConfig, null, hub.remoteNodeValidator()); channelProvider.add(tcpReplicator); } @@ -85,6 +85,18 @@ final class ChannelProvider implements Closeable { private static final byte BOOTSTRAP_MESSAGE = 'B'; final EntryExternalizable asEntryExternalizable = new EntryExternalizable() { + @Override + public int sizeOfEntry(@NotNull Bytes entry, int chronicleChannel) { + channelDataLock.readLock().lock(); + try { + + return channelEntryExternalizables[chronicleChannel] + .sizeOfEntry(entry, chronicleChannel); + } finally { + channelDataLock.readLock().unlock(); + } + } + /** * writes the entry to the chronicle channel provided * @@ -116,7 +128,7 @@ final class ChannelProvider implements Closeable { final int chronicleId = (int) source.readStopBit(); if (chronicleId < chronicleChannels.length) { // this channel is has not currently been created so it updates will be ignored - if (channelEntryExternalizables[chronicleId] != null) + if (channelEntryExternalizables[chronicleId] != null) channelEntryExternalizables[chronicleId] .readExternalEntry(copies, segmentState, source); } else @@ -398,8 +410,8 @@ final class ChannelProvider implements Closeable { } /** - * used to send system messages such as bootstrap from one remote node to another, it also can be used in - * a broadcast context + * used to send system messages such as bootstrap from one remote node to another, it also can + * be used in a broadcast context */ class SystemQueue { @@ -467,6 +479,11 @@ final class ChannelProvider implements Closeable { } }; final EntryExternalizable asEntryExternalizable = new EntryExternalizable() { + @Override + public int sizeOfEntry(@NotNull Bytes entry, int chronicleId) { + return (int) entry.remaining(); + } + @Override public void writeExternalEntry(@NotNull Bytes entry, @NotNull Bytes destination, int na) { diff --git a/src/main/java/net/openhft/chronicle/map/Replica.java b/src/main/java/net/openhft/chronicle/map/Replica.java index 7ceb3b66b..9f328e74f 100644 --- a/src/main/java/net/openhft/chronicle/map/Replica.java +++ b/src/main/java/net/openhft/chronicle/map/Replica.java @@ -30,39 +30,43 @@ import java.io.Closeable; interface Replica extends Closeable { /** - * Provides the unique Identifier associated with this map instance.
An identifier is used to - * determine which replicating node made the change.
If two nodes update their map at the same time - * with different values, we have to deterministically resolve which update wins, because of eventual - * consistency both nodes should end up locally holding the same data. Although it is rare two remote - * nodes could receive an update to their maps at exactly the same time for the same key, we have to - * handle this edge case, its therefore important not to rely on timestamps alone to reconcile the - * updates. Typically the update with the newest timestamp should win, but in this example both - * timestamps are the same, and the decision made to one node should be identical to the decision made to - * the other. We resolve this simple dilemma by using a node identifier, each node will have a unique - * identifier, the update from the node with the smallest identifier wins. + * Provides the unique Identifier associated with this map instance.
An identifier is used + * to determine which replicating node made the change.
If two nodes update their map at the + * same time with different values, we have to deterministically resolve which update wins, + * because of eventual consistency both nodes should end up locally holding the same data. + * Although it is rare two remote nodes could receive an update to their maps at exactly the + * same time for the same key, we have to handle this edge case, its therefore important not to + * rely on timestamps alone to reconcile the updates. Typically the update with the newest + * timestamp should win, but in this example both timestamps are the same, and the decision + * made to one node should be identical to the decision made to the other. We resolve this + * simple dilemma by using a node identifier, each node will have a unique identifier, the + * update from the node with the smallest identifier wins. * * @return the unique Identifier associated with this map instance */ byte identifier(); /** - * Gets (if it does not exist, creates) an instance of ModificationIterator associated with a remote node, - * this weak associated is bound using the {@code identifier}. + * Gets (if it does not exist, creates) an instance of ModificationIterator associated with a + * remote node, this weak associated is bound using the {@code identifier}. * * @param remoteIdentifier the identifier of the remote node - * @param modificationNotifier called when ever there is a change applied to the modification iterator - * @return the ModificationIterator dedicated for replication to the remote node with the given identifier + * @param modificationNotifier called when ever there is a change applied to the modification + * iterator + * @return the ModificationIterator dedicated for replication to the remote node with the given + * identifier * @see #identifier() */ ModificationIterator acquireModificationIterator(byte remoteIdentifier, ModificationNotifier modificationNotifier); /** - * Returns the timestamp of the last change from the specified remote node, already replicated to this - * Replica.
Used in conjunction with replication, to back fill data from a remote node. This node - * may have missed updates while it was not been running or connected via TCP. + * Returns the timestamp of the last change from the specified remote node, already replicated + * to this Replica.
Used in conjunction with replication, to back fill data from a remote + * node. This node may have missed updates while it was not been running or connected via TCP. * - * @param remoteIdentifier the identifier of the remote node to check last replicated update time from + * @param remoteIdentifier the identifier of the remote node to check last replicated update + * time from * @return a timestamp of the last modification to an entry, or 0 if there are no entries. * @see #identifier() */ @@ -97,20 +101,21 @@ interface Replica extends Closeable { boolean hasNext(); /** - * A non-blocking call that provides the entry that has changed to {@code callback.onEntry()}. + * A non-blocking call that provides the entry that has changed to {@code + * callback.onEntry()}. * * @param callback a callback which will be called when a new entry becomes available. * @param chronicleId only assigned when using chronicle channels - * @return {@code true} if the entry was accepted by the {@code callback.onEntry()} method, {@code - * false} if the entry was not accepted or was not available + * @return {@code true} if the entry was accepted by the {@code callback.onEntry()} method, + * {@code false} if the entry was not accepted or was not available */ boolean nextEntry(@NotNull final EntryCallback callback, final int chronicleId); /** - * Dirties all entries with a modification time equal to {@code fromTimeStamp} or newer. It means all - * these entries will be considered as "new" by this ModificationIterator and iterated once again no - * matter if they have already been.
This functionality is used to publish recently modified - * entries to a new remote node as it connects. + * Dirties all entries with a modification time equal to {@code fromTimeStamp} or newer. It + * means all these entries will be considered as "new" by this ModificationIterator and + * iterated once again no matter if they have already been.
This functionality is used
+ * to publish recently modified entries to a new remote node as it connects.
*
* @param fromTimeStamp the timestamp from which all entries should be dirty
*/
@@ -122,25 +127,35 @@ interface Replica extends Closeable {
*/
interface EntryExternalizable {
+ /**
+ * @param entry an entry in the map
+ * @param chronicleId is the channel id used to identify the canonical map or queue
+ * @return the size of the entry
+ */
+ int sizeOfEntry(@NotNull Bytes entry, int chronicleId);
+
/**
* The map implements this method to save its contents.
*
* @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 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(@NotNull Bytes entry, @NotNull Bytes destination, int chronicleId);
+ void writeExternalEntry(@NotNull Bytes entry, @NotNull Bytes destination, int
+ chronicleId);
/**
- * The map implements this method to restore its contents. This method must read the values in the
- * same sequence and with the same types as were written by {@code writeExternalEntry()}. This method
- * is typically called when we receive a remote replication event, this event could originate from
- * either a remote {@code put(K key, V value)} or {@code remove(Object key)}
+ * The map implements this method to restore its contents. This method must read the values
+ * in the same sequence and with the same types as were written by {@code
+ * writeExternalEntry()}. This method is typically called when we receive a remote
+ * replication event, this event could originate from either a remote {@code put(K key, V
+ *value)} or {@code remove(Object key)}
*
* @param copies
* @param segmentState
- * @param source bytes to read an entry from
+ * @param source bytes to read an entry from
*/
void readExternalEntry(
@NotNull ThreadLocalCopies copies,
@@ -148,8 +163,8 @@ interface Replica extends Closeable {
}
/**
- * provides a key and value from NativeBytes, this can be used in conjunction with the modification
- * iterator to get the key and value out of the NativeBytes
+ * provides a key and value from NativeBytes, this can be used in conjunction with the
+ * modification iterator to get the key and value out of the NativeBytes
*/
interface EntryResolver