/*
* 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.Jvm;
import net.openhft.chronicle.core.annotation.UsedViaReflection;
import net.openhft.chronicle.core.threads.EventLoop;
import net.openhft.chronicle.engine.api.tree.Asset;
import net.openhft.chronicle.engine.fs.Clusters;
import net.openhft.chronicle.engine.fs.EngineCluster;
import net.openhft.chronicle.engine.tree.HostIdentifier;
import net.openhft.chronicle.network.api.session.SubHandler;
import net.openhft.chronicle.network.cluster.*;
import net.openhft.chronicle.network.connection.WireOutPublisher;
import net.openhft.chronicle.threads.NamedThreadFactory;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static java.util.concurrent.TimeUnit.SECONDS;
import static net.openhft.chronicle.network.HeaderTcpHandler.HANDLER;
import static net.openhft.chronicle.network.cluster.TerminatorHandler.terminationHandler;
/**
* Created by Rob Austin
*/
public class UberHandler extends CspTcpHander<EngineWireNetworkContext>
implements Demarshallable, WriteMarshallable {
private final int remoteIdentifier;
private final int localIdentifier;
@NotNull
private AtomicBoolean isClosing = new AtomicBoolean();
private ConnectionChangedNotifier connectionChangedNotifier;
private Asset rootAsset;
@NotNull
private String clusterName;
private int writerIndex;
@UsedViaReflection
private UberHandler(@NotNull WireIn wire) {
remoteIdentifier = wire.read(() -> "remoteIdentifier").int32();
localIdentifier = wire.read(() -> "localIdentifier").int32();
@Nullable final WireType wireType = wire.read(() -> "wireType").object(WireType.class);
clusterName = wire.read(() -> "clusterName").text();
wireType(wireType);
}
private UberHandler(int localIdentifier,
int remoteIdentifier,
@NotNull WireType wireType,
@NotNull String clusterName) {
this.localIdentifier = localIdentifier;
this.remoteIdentifier = remoteIdentifier;
assert remoteIdentifier != localIdentifier :
"remoteIdentifier=" + remoteIdentifier + ", " +
"localIdentifier=" + localIdentifier;
this.clusterName = clusterName;
wireType(wireType);
}
private static WriteMarshallable uberHandler(final WriteMarshallable m) {
return wire -> {
try (final DocumentContext dc = wire.writingDocument(true)) {
wire.write(() -> HANDLER).typedMarshallable(m);
}
};
}
public int remoteIdentifier() {
return remoteIdentifier;
}
public boolean isClosed() {
return isClosing.get();
}
@Override
public void writeMarshallable(@NotNull WireOut wire) {
wire.write(() -> "remoteIdentifier").int32(localIdentifier);
wire.write(() -> "localIdentifier").int32(remoteIdentifier);
final WireType value = wireType();
wire.write(() -> "wireType").object(value);
wire.write(() -> "clusterName").text(clusterName);
}
@Override
protected void onInitialize() {
EngineWireNetworkContext nc = nc();
nc.wireType(wireType());
isAcceptor(nc.isAcceptor());
rootAsset = nc.rootAsset();
assert checkIdentifierEqualsHostId();
assert remoteIdentifier != localIdentifier :
"remoteIdentifier=" + remoteIdentifier + ", " +
"localIdentifier=" + localIdentifier;
final WireOutPublisher publisher = nc.wireOutPublisher();
publisher(publisher);
@Nullable EventLoop eventLoop = rootAsset.findOrCreateView(EventLoop.class);
if (!eventLoop.isClosed()) {
eventLoop.start();
@Nullable final Clusters clusters = rootAsset.findView(Clusters.class);
final EngineCluster engineCluster = clusters.get(clusterName);
if (engineCluster == null) {
Jvm.warn().on(getClass(), "cluster=" + clusterName, new RuntimeException("cluster not " +
"found, cluster=" + clusterName));
return;
}
// note : we have to publish the uber handler, even if we send a termination event
// this is so the termination event can be processed by the receiver
if (nc().isAcceptor())
// reflect the uber handler
publish(uberHandler());
nc.terminationEventHandler(engineCluster.findTerminationEventHandler(remoteIdentifier));
if (!checkConnectionStrategy(engineCluster)) {
// the strategy has told us to reject this connection, we have to first notify the
// other host, we will do this by sending a termination event
publish(terminationHandler(localIdentifier, remoteIdentifier, nc.newCid()));
closeSoon();
return;
}
if (!isClosing.get())
notifyConnectionListeners(engineCluster);
}
}
private boolean checkIdentifierEqualsHostId() {
@Nullable final HostIdentifier hostIdentifier = rootAsset.findOrCreateView(HostIdentifier.class);
return hostIdentifier == null || localIdentifier == hostIdentifier.hostId();
}
private void notifyConnectionListeners(@NotNull EngineCluster cluster) {
connectionChangedNotifier = cluster.findClusterNotifier(remoteIdentifier);
if (connectionChangedNotifier != null)
connectionChangedNotifier.onConnectionChanged(true, nc());
}
private boolean checkConnectionStrategy(@NotNull EngineCluster cluster) {
final ConnectionStrategy strategy = cluster.findConnectionStrategy(remoteIdentifier);
return strategy == null ||
strategy.notifyConnected(this, localIdentifier, remoteIdentifier);
}
private WriteMarshallable uberHandler() {
@NotNull final UberHandler handler = new UberHandler(
localIdentifier,
remoteIdentifier,
wireType(),
clusterName);
return uberHandler(handler);
}
/**
* wait 2 seconds before closing the socket connection, this should allow time of the
* termination event to be sent.
*/
private void closeSoon() {
isClosing.set(true);
@NotNull ScheduledExecutorService closer = newSingleThreadScheduledExecutor(new NamedThreadFactory("closer", true));
closer.schedule(() -> {
closer.shutdown();
close();
}, 2, SECONDS);
}
@Override
public void close() {
if (!isClosing.getAndSet(true) && connectionChangedNotifier != null)
connectionChangedNotifier.onConnectionChanged(false, nc());
nc().acquireConnectionListener().onDisconnected(localIdentifier, remoteIdentifier(), nc().isAcceptor());
super.close();
}
@Override
protected void onRead(@NotNull DocumentContext dc, @NotNull WireOut outWire) {
try {
if (isClosing.get())
return;
onMessageReceivedOrWritten();
Wire inWire = dc.wire();
if (dc.isMetaData()) {
if (!readMeta(inWire))
return;
SubHandler handler = handler();
handler.remoteIdentifier(remoteIdentifier);
handler.localIdentifier(localIdentifier);
try {
handler.onInitialize(outWire);
} catch (RejectedExecutionException e) {
throw new IllegalStateException("EventGroup shutdown", e);
}
return;
}
SubHandler handler = handler();
if (handler == null)
throw new IllegalStateException("handler == null, check that the " +
"Csp/Cid has been sent, failed to " +
"fully " +
"process the following " +
"YAML\n");
if (dc.isData() && !inWire.bytes().isEmpty())
handler.onRead(inWire, outWire);
} catch (Throwable e) {
Jvm.warn().on(getClass(), "failed to parse:" + dc.wire().readingPeekYaml(), e);
}
}
@Override
protected void onBytesWritten() {
onMessageReceivedOrWritten();
}
/**
* ready to accept wire
*
* @param outWire the wire that you wish to write
*/
@Override
protected void onWrite(@NotNull WireOut outWire) {
SubHandler handler = handler();
if (handler != null)
handler.onWrite(outWire);
for (int i = 0; i < writers.size(); i++) {
if (isClosing.get())
return;
WriteMarshallable w = next();
if (w != null)
w.writeMarshallable(outWire);
}
}
/**
* round robbins - the writers, we should only write when the buffer is empty, as // we can't
* guarantee that we will have enough space to add more data to the out wire.
*
* @return the Marshallable that you are writing to
*/
private WriteMarshallable next() {
if (writerIndex >= writers.size())
writerIndex = 0;
return writers.get(writerIndex++);
}
private void onMessageReceivedOrWritten() {
final HeartbeatEventHandler heartbeatEventHandler = heartbeatEventHandler();
if (heartbeatEventHandler != null)
heartbeatEventHandler.onMessageReceived();
}
public static class Factory implements BiFunction<ClusterContext, HostDetails,
WriteMarshallable>, Demarshallable {
@UsedViaReflection
private Factory(@NotNull WireIn wireIn) {
}
public Factory() {
}
@NotNull
@Override
public WriteMarshallable apply(@NotNull final ClusterContext clusterContext,
@NotNull final HostDetails hostdetails) {
final byte localIdentifier = clusterContext.localIdentifier();
final int remoteIdentifier = hostdetails.hostId();
final WireType wireType = clusterContext.wireType();
final String name = clusterContext.clusterName();
return uberHandler(new UberHandler(localIdentifier, remoteIdentifier, wireType, name));
}
}
}