/* * 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.server.internal; import net.openhft.chronicle.core.annotation.UsedViaReflection; import net.openhft.chronicle.core.io.Closeable; import net.openhft.chronicle.core.threads.EventHandler; import net.openhft.chronicle.core.threads.EventLoop; import net.openhft.chronicle.core.threads.HandlerPriority; import net.openhft.chronicle.core.threads.InvalidEventHandlerException; import net.openhft.chronicle.engine.api.EngineReplication.ModificationIterator; import net.openhft.chronicle.engine.api.pubsub.Replication; import net.openhft.chronicle.engine.api.tree.Asset; import net.openhft.chronicle.engine.api.tree.RequestContext; import net.openhft.chronicle.engine.map.CMap2EngineReplicator.VanillaReplicatedEntry; import net.openhft.chronicle.engine.tree.HostIdentifier; import net.openhft.chronicle.network.cluster.AbstractSubHandler; import net.openhft.chronicle.network.connection.CoreFields; import net.openhft.chronicle.network.connection.WireOutPublisher; import net.openhft.chronicle.wire.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.lang.ThreadLocal.withInitial; import static net.openhft.chronicle.engine.server.internal.MapReplicationHandler.EventId.replicationEvent; import static net.openhft.chronicle.network.connection.CoreFields.lastUpdateTime; /** * Created by Rob Austin */ public class MapReplicationHandler extends AbstractSubHandler<EngineWireNetworkContext> implements Demarshallable, WriteMarshallable { private static final Logger LOG = LoggerFactory.getLogger(MapReplicationHandler.class); private final ThreadLocal<VanillaReplicatedEntry> vre = withInitial(VanillaReplicatedEntry::new); private Replication replication; private long timestamp; private byte localIdentifier; private volatile boolean closed; @NotNull private Class keyType; @NotNull private Class valueType; @UsedViaReflection private MapReplicationHandler(@NotNull WireIn wire) { timestamp = wire.read(() -> "timestamp").int64(); keyType = wire.read(() -> "keyType").typeLiteral(); valueType = wire.read(() -> "valueType").typeLiteral(); } private MapReplicationHandler(long timestamp, @NotNull Class keyType, @NotNull Class valueType) { this.timestamp = timestamp; this.keyType = keyType; this.valueType = valueType; } @NotNull public static WriteMarshallable newMapReplicationHandler(long lastUpdateTime, @NotNull Class keyType, @NotNull Class valueType, String csp, long cid) { @NotNull final MapReplicationHandler h = new MapReplicationHandler (lastUpdateTime, keyType, valueType); return w -> w.writeDocument(true, d -> d.writeEventName(CoreFields.csp).text(csp) .writeEventName(CoreFields.cid).int64(cid) .writeEventName(CoreFields.handler).typedMarshallable(h)); } @Override public void writeMarshallable(@NotNull WireOut wire) { wire.write("timestamp").int64(timestamp); wire.write("keyType").typeLiteral(keyType); wire.write("valueType").typeLiteral(valueType); } @Override public void onRead(@NotNull WireIn inWire, @NotNull WireOut outWire) { final StringBuilder eventName = Wires.acquireStringBuilder(); @NotNull final ValueIn valueIn = inWire.readEventName(eventName); if (lastUpdateTime.contentEquals(eventName)) { final long time = valueIn.int64(); final byte id = inWire.read(() -> "id").int8(); replication.setLastModificationTime(id, time); return; } // receives replication events if (replicationEvent.contentEquals(eventName)) { final VanillaReplicatedEntry entry = vre.get(); entry.clear(); valueIn.marshallable(entry); replication.applyReplication(entry); } } @Override public void onInitialize(@NotNull WireOut outWire) { if (isClosed()) return; @NotNull Asset rootAsset = nc().rootAsset(); @NotNull final RequestContext requestContext = RequestContext.requestContext(csp()); @NotNull final Asset asset = rootAsset.acquireAsset(requestContext.fullName()); replication = asset.acquireView(Replication.class, RequestContext.requestContext(asset .fullName()).keyType(keyType).valueType(valueType)); // reflect back the map replication handler final long lastUpdateTime = replication.lastModificationTime ((byte) remoteIdentifier()); @NotNull WriteMarshallable writeMarshallable = newMapReplicationHandler(lastUpdateTime, keyType, valueType, csp(), cid()); publish(writeMarshallable); @Nullable final HostIdentifier hostIdentifier = rootAsset.findOrCreateView(HostIdentifier.class); if (hostIdentifier != null) localIdentifier = hostIdentifier.hostId(); @Nullable EventLoop eventLoop = rootAsset.findOrCreateView(EventLoop.class); eventLoop.start(); @Nullable final ModificationIterator mi = replication.acquireModificationIterator( (byte) remoteIdentifier()); if (mi != null) mi.dirtyEntries(timestamp); if (mi == null) return; // sends replication events back to the remote client mi.setModificationNotifier(eventLoop::unpause); if (!eventLoop.isAlive() && !eventLoop.isClosed()) throw new IllegalStateException("the event loop is not yet running !"); eventLoop.addHandler(true, new ReplicationEventHandler(mi, (byte) remoteIdentifier())); } @Override public void close() { this.closed = true; } public enum EventId implements ParameterizeWireKey { replicationEvent, bootstrap; private final WireKey[] params; @SafeVarargs <P extends WireKey> EventId(P... params) { this.params = params; } @NotNull public <P extends WireKey> P[] params() { //noinspection unchecked return (P[]) this.params; } } private class ReplicationEventHandler implements EventHandler, Closeable { private final ModificationIterator mi; private final byte id; boolean hasSentLastUpdateTime; long lastUpdateTime; boolean hasLogged; int count; long startBufferFullTimeStamp; ReplicationEventHandler(ModificationIterator mi, byte id) { this.mi = mi; this.id = id; lastUpdateTime = 0; hasLogged = false; count = 0; startBufferFullTimeStamp = 0; } @NotNull @Override public HandlerPriority priority() { return HandlerPriority.REPLICATION; } @Override public boolean action() throws InvalidEventHandlerException { if (closed || nc().isClosed()) throw new InvalidEventHandlerException(); final WireOutPublisher publisher = nc().wireOutPublisher(); assert !closed; if (publisher.isClosed()) throw new InvalidEventHandlerException("publisher is closed"); if (!mi.hasNext()) { // because events arrive in a bitset ( aka random ) order ( not necessary in // time order ) we can only be assured that the latest time of // the last event is really the latest time, once all the events // have been received, we know when we have received all events // when there are no more events to process. if (!hasSentLastUpdateTime && lastUpdateTime > 0) { publisher.put(null, w -> { w.writeDocument(true, d -> d.write(CoreFields.cid).int64(cid())); w.writeDocument(false, d -> { d.writeEventName(CoreFields.lastUpdateTime).int64(lastUpdateTime); d.write(() -> "id").int8(id); }); }); hasSentLastUpdateTime = true; } return false; } mi.nextEntry(e -> publisher.put(null, w -> { assert e.remoteIdentifier() != localIdentifier; long newlastUpdateTime = Math.max(lastUpdateTime, e.timestamp()); if (newlastUpdateTime > lastUpdateTime) { hasSentLastUpdateTime = false; lastUpdateTime = newlastUpdateTime; } w.writeDocument(true, d -> d.write(CoreFields.cid).int64(cid())); w.writeDocument(false, d -> { d.writeEventName(replicationEvent).marshallable(e); d.writeComment("isAcceptor=" + nc().isAcceptor()); }); })); return true; } @NotNull @Override public String toString() { return "ReplicationEventHandler{" + "id=" + id + ",connectionClosed=" + nc().isClosed() + '}'; } @Override public void close() { MapReplicationHandler.this.close(); } } }