/* * Copyright 2016 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.Bytes; import net.openhft.chronicle.bytes.BytesStore; import net.openhft.chronicle.bytes.PointerBytesStore; import net.openhft.chronicle.core.Jvm; import net.openhft.chronicle.core.pool.ClassAliasPool; import net.openhft.chronicle.engine.api.EngineReplication; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.map.replication.Bootstrap; import net.openhft.chronicle.hash.replication.EngineReplicationLangBytesConsumer; import net.openhft.chronicle.map.EngineReplicationLangBytes; import net.openhft.chronicle.map.EngineReplicationLangBytes.EngineModificationIterator; import net.openhft.chronicle.wire.TextWire; import net.openhft.chronicle.wire.WireIn; import net.openhft.chronicle.wire.Wires; import net.openhft.lang.io.NativeBytes; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.function.Consumer; import static java.lang.ThreadLocal.withInitial; /** * Created by Rob Austin */ public class CMap2EngineReplicator implements EngineReplication, EngineReplicationLangBytesConsumer { private static final Logger LOG = LoggerFactory.getLogger(CMap2EngineReplicator.class); static { ClassAliasPool.CLASS_ALIASES.addAlias(VanillaReplicatedEntry.class); ClassAliasPool.CLASS_ALIASES.addAlias(Bootstrap.class); } final ThreadLocal<KvBytes> kvBytesThreadLocal = ThreadLocal.withInitial(KvBytes::new); private final RequestContext context; private final ThreadLocal<PointerBytesStore> keyLocal = withInitial(PointerBytesStore::new); private final ThreadLocal<PointerBytesStore> valueLocal = withInitial(PointerBytesStore::new); private final ThreadLocal<KvLangBytes> kvByte = ThreadLocal.withInitial(KvLangBytes::new); private EngineReplicationLangBytes engineReplicationLang; public CMap2EngineReplicator(RequestContext requestContext, @NotNull Asset asset) { this(requestContext); asset.addView(EngineReplicationLangBytesConsumer.class, this); } public CMap2EngineReplicator(final RequestContext context) { this.context = context; } @Override public void set(@NotNull final EngineReplicationLangBytes engineReplicationLangBytes) { this.engineReplicationLang = engineReplicationLangBytes; } @NotNull private net.openhft.lang.io.Bytes toLangBytes(@NotNull BytesStore b, @NotNull Bytes tmpBytes, @NotNull net.openhft.lang.io.NativeBytes lb) { if (b.isDirectMemory()) { // check(b); lb.setStartPositionAddress(b.address(b.start()), b.address(b.readLimit())); // check(lb); } else { tmpBytes.clear(); tmpBytes.write(b); lb.setStartPositionAddress(tmpBytes.address(tmpBytes.start()), tmpBytes.address(tmpBytes.readLimit())); } return lb; } private void check(@NotNull BytesStore b) { for (long i = b.start(); i < b.readLimit(); i++) { int ch = b.readByte(i); if (ch < ' ') throw new AssertionError("Char " + ch); } } private void check(@NotNull net.openhft.lang.io.Bytes b) { if (b.position() != 0) throw new AssertionError(); if (b.remaining() != b.limit()) throw new AssertionError(); for (long i = 0; i < 16 && i < b.limit(); i++) { int ch = b.readByte(i); if (ch < ' ') throw new AssertionError("Char " + ch); } if (b.limit() > 32) for (long i = b.limit() - 16; i < b.limit(); i++) { int ch = b.readByte(i); if (ch < ' ') throw new AssertionError("Char " + ch); } } public void put(@NotNull final BytesStore key, @NotNull final BytesStore value, final byte remoteIdentifier, final long timestamp) { // assert key.refCount() == 1; // assert value.refCount()== 1; final KvLangBytes kv = kvByte.get(); @NotNull net.openhft.lang.io.Bytes keyBytes = toLangBytes(key, kv.tmpKeyBytes, kv.key); @NotNull net.openhft.lang.io.Bytes valueBytes = toLangBytes(value, kv.tmpValueBytes, kv.value); engineReplicationLang.put(keyBytes, valueBytes, remoteIdentifier, timestamp); keyBytes.position(0); valueBytes.position(0); } private void remove(@NotNull final BytesStore key, final byte remoteIdentifier, final long timestamp) { KvLangBytes kv = kvByte.get(); @NotNull net.openhft.lang.io.Bytes keyBytes = toLangBytes(key, kv.tmpKeyBytes, kv.key); engineReplicationLang.remove(keyBytes, remoteIdentifier, timestamp); } @Override public byte identifier() { return engineReplicationLang.identifier(); } private void put(@NotNull final ReplicationEntry entry) { put(entry.key(), entry.value(), entry.identifier(), entry.timestamp()); } private void remove(@NotNull final ReplicationEntry entry) { remove(entry.key(), entry.identifier(), entry.timestamp()); } @Override public void applyReplication(@NotNull final ReplicationEntry entry) { if (LOG.isDebugEnabled()) Jvm.debug().on(getClass(), "applyReplication entry=" + entry); if (entry.isDeleted()) remove(entry); else put(entry); } @Nullable @Override public ModificationIterator acquireModificationIterator(final byte remoteIdentifier) { final EngineModificationIterator instance = engineReplicationLang .acquireEngineModificationIterator(remoteIdentifier); return new ModificationIterator() { public boolean hasNext() { return instance.hasNext(); } public boolean nextEntry(@NotNull Consumer<ReplicationEntry> consumer) { return nextEntry(entry -> { consumer.accept(entry); return true; }); } boolean nextEntry(@NotNull final EntryCallback callback) { return instance.nextEntry((key, value, timestamp, identifier, isDeleted, bootStrapTimeStamp) -> { final KvBytes threadLocal = kvBytesThreadLocal.get(); @NotNull VanillaReplicatedEntry entry = new VanillaReplicatedEntry( toKey(key, threadLocal.key(key.remaining())), toValue(value, threadLocal.value(value.remaining())), timestamp, identifier, isDeleted, bootStrapTimeStamp, remoteIdentifier); return callback.onEntry(entry); }); } @NotNull private PointerBytesStore toKey(final @NotNull net.openhft.lang.io.Bytes key, @NotNull final PointerBytesStore pbs) { pbs.set(key.address(), key.capacity()); return pbs; } @Nullable private BytesStore toValue(final @Nullable net.openhft.lang.io.Bytes value, @NotNull final PointerBytesStore pbs) { if (value == null) return null; pbs.set(value.address(), value.capacity()); return pbs; } @Override public void dirtyEntries(final long fromTimeStamp) { instance.dirtyEntries(fromTimeStamp); } @Override public void setModificationNotifier( @NotNull final ModificationNotifier modificationNotifier) { instance.setModificationNotifier(modificationNotifier::onChange); } }; } /** * @param remoteIdentifier the identifier of the remote node to check last replicated update * time from * @return the last time that host denoted by the {@code remoteIdentifier} was updated in * milliseconds. */ @Override public long lastModificationTime(final byte remoteIdentifier) { return engineReplicationLang.lastModificationTime(remoteIdentifier); } /** * @param identifier the identifier of the remote node to check last replicated update time * from * @param timestamp set the last time that host denoted by the {@code remoteIdentifier} was * updated in milliseconds. */ @Override public void setLastModificationTime(final byte identifier, final long timestamp) { engineReplicationLang.setLastModificationTime(identifier, timestamp); } @NotNull @Override public String toString() { return "CMap2EngineReplicator{" + "context=" + context + ", identifier=" + engineReplicationLang.identifier() + ", keyLocal=" + keyLocal + ", valueLocal=" + valueLocal + '}'; } static class KvLangBytes { final NativeBytes key = NativeBytes.empty(); final NativeBytes value = NativeBytes.empty(); final Bytes tmpKeyBytes = Bytes.allocateElasticDirect(); final Bytes tmpValueBytes = Bytes.allocateElasticDirect(); } private static class KvBytes { private final PointerBytesStore key = BytesStore.nativePointer(); private final PointerBytesStore value = BytesStore.nativePointer(); @NotNull private PointerBytesStore key(long size) { return key; } @NotNull private PointerBytesStore value(long size) { return value; } } public static class VanillaReplicatedEntry implements ReplicationEntry { private final byte remoteIdentifier; private BytesStore key; @Nullable private BytesStore value; private long timestamp; private byte identifier; private boolean isDeleted; private long bootStrapTimeStamp; /** * @param key the key of the entry * @param value the value of the entry * @param timestamp the timestamp send from the remote server, this time stamp was * the time the entry was removed * @param identifier the identifier of the remote server * @param bootStrapTimeStamp sent to the client on every update this is the timestamp that * the remote client should bootstrap from when there has been a * @param remoteIdentifier the identifier of the server we are sending data to ( only used * as a comment ) */ VanillaReplicatedEntry(@NotNull final BytesStore key, @Nullable final BytesStore value, final long timestamp, final byte identifier, final boolean isDeleted, final long bootStrapTimeStamp, byte remoteIdentifier) { this.key = key; this.remoteIdentifier = remoteIdentifier; // must be native assert key.underlyingObject() == null; this.value = value; // must be native assert value == null || value.underlyingObject() == null; this.timestamp = timestamp; this.identifier = identifier; this.isDeleted = isDeleted; this.bootStrapTimeStamp = bootStrapTimeStamp; } // for deserialization only. public VanillaReplicatedEntry() { remoteIdentifier = 0; key = BytesStore.nativePointer(); value = BytesStore.nativePointer(); } public void clear() { key.isPresent(false); value.isPresent(false); timestamp = 0; identifier = 0; isDeleted = false; bootStrapTimeStamp = 0; } @Override public void readMarshallable(@NotNull WireIn wire) { wire.read(() -> "key").bytesSet((PointerBytesStore) key); wire.read(() -> "value").bytesSet((PointerBytesStore) value); timestamp(wire.read(() -> "timestamp").int64()); identifier(wire.read(() -> "identifier").int8()); isDeleted(wire.read(() -> "isDeleted").bool()); bootStrapTimeStamp(wire.read(() -> "bootStrapTimeStamp").int64()); } @Override public BytesStore key() { return key; } @Nullable @Override public BytesStore value() { return value != null && value.isPresent() ? value : null; } @Override public long timestamp() { return timestamp; } @Override public byte identifier() { return identifier; } @Override public byte remoteIdentifier() { return remoteIdentifier; } @Override public boolean isDeleted() { return isDeleted; } @Override public long bootStrapTimeStamp() { return bootStrapTimeStamp; } @Override public void key(BytesStore key) { this.key = key; } @Override public void value(BytesStore value) { this.value = value; } @Override public void timestamp(long timestamp) { this.timestamp = timestamp; } @Override public void identifier(byte identifier) { this.identifier = identifier; } @Override public void isDeleted(boolean isDeleted) { this.isDeleted = isDeleted; } @Override public void bootStrapTimeStamp(long bootStrapTimeStamp) { this.bootStrapTimeStamp = bootStrapTimeStamp; } @NotNull @Override public String toString() { final Bytes<ByteBuffer> bytes = Bytes.elasticByteBuffer(); new TextWire(bytes).writeDocument(false, d -> d.write().typedMarshallable(this)); return "\n" + Wires.fromSizePrefixedBlobs(bytes); } } }