/*
* 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;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.annotation.UsedViaReflection;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.threads.EventLoop;
import net.openhft.chronicle.core.threads.InvalidEventHandlerException;
import net.openhft.chronicle.core.threads.Timer;
import net.openhft.chronicle.core.threads.VanillaEventHandler;
import net.openhft.chronicle.engine.server.internal.EngineWireNetworkContext;
import net.openhft.chronicle.network.ConnectionListener;
import net.openhft.chronicle.network.cluster.AbstractSubHandler;
import net.openhft.chronicle.network.cluster.ClusterContext;
import net.openhft.chronicle.network.cluster.HeartbeatEventHandler;
import net.openhft.chronicle.network.connection.CoreFields;
import net.openhft.chronicle.network.connection.WireOutPublisher;
import net.openhft.chronicle.wire.Demarshallable;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import net.openhft.chronicle.wire.WriteMarshallable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
/**
* will periodically send a heartbeat message, the period of this message is defined by {@link
* HeartbeatHandler#heartbeatIntervalMs} once the heart beat is
*
* @author Rob Austin.
*/
public class HeartbeatHandler<T extends EngineWireNetworkContext> extends AbstractSubHandler<T> implements
Demarshallable, WriteMarshallable, HeartbeatEventHandler {
static final Logger LOG = LoggerFactory.getLogger(SimpleEngineMain.class);
private final long heartbeatIntervalMs;
private final long heartbeatTimeoutMs;
private final AtomicBoolean hasHeartbeats = new AtomicBoolean();
private volatile long lastTimeMessageReceived;
@Nullable
private ConnectionListener connectionMonitor;
@Nullable
private Timer timer;
@UsedViaReflection
public HeartbeatHandler(@NotNull WireIn w) {
heartbeatTimeoutMs = w.read(() -> "heartbeatTimeoutMs").int64();
heartbeatIntervalMs = w.read(() -> "heartbeatIntervalMs").int64();
assert heartbeatTimeoutMs >= 1000 :
"heartbeatTimeoutMs=" + heartbeatTimeoutMs + ", this is too small";
assert heartbeatIntervalMs >= 500 :
"heartbeatIntervalMs=" + heartbeatIntervalMs + ", this is too small";
onMessageReceived();
}
private HeartbeatHandler(long heartbeatTimeoutMs, long heartbeatIntervalMs) {
this.heartbeatTimeoutMs = heartbeatTimeoutMs;
this.heartbeatIntervalMs = heartbeatIntervalMs;
assert heartbeatTimeoutMs > heartbeatIntervalMs :
"heartbeatIntervalMs=" + heartbeatIntervalMs + ", " +
"heartbeatTimeoutMs=" + heartbeatTimeoutMs;
assert heartbeatTimeoutMs >= 1000 :
"heartbeatTimeoutMs=" + heartbeatTimeoutMs + ", this is too small";
assert heartbeatIntervalMs >= 500 :
"heartbeatIntervalMs=" + heartbeatIntervalMs + ", this is too small";
}
private static WriteMarshallable heartbeatHandler(final long heartbeatTimeoutMs,
final long heartbeatIntervalMs,
final long cid) {
return w -> w.writeDocument(true,
d -> d.writeEventName(CoreFields.csp).text("/")
.writeEventName(CoreFields.cid).int64(cid)
.writeEventName(CoreFields.handler).typedMarshallable(new
HeartbeatHandler(heartbeatTimeoutMs, heartbeatIntervalMs)));
}
@Override
public void onInitialize(@NotNull WireOut outWire) {
if (nc().isAcceptor())
heartbeatHandler(heartbeatTimeoutMs, heartbeatIntervalMs, cid()).writeMarshallable
(outWire);
@NotNull final WriteMarshallable heartbeatMessage = w -> {
w.writeDocument(true, d -> d.write(CoreFields.cid).int64(cid()));
w.writeDocument(false, d -> d.write(() -> "heartbeat").text(""));
};
connectionMonitor = nc().acquireConnectionListener();
timer = new Timer(nc().rootAsset().findOrCreateView(EventLoop.class));
startPeriodicHeartbeatCheck();
startPeriodicallySendingHeartbeats(heartbeatMessage);
}
private void startPeriodicallySendingHeartbeats(WriteMarshallable heartbeatMessage) {
@NotNull final VanillaEventHandler task = () -> {
if (isClosed())
throw new InvalidEventHandlerException("closed");
// we will only publish a heartbeat if the wire out publisher is empty
WireOutPublisher wireOutPublisher = nc().wireOutPublisher();
if (wireOutPublisher.isEmpty())
wireOutPublisher.publish(heartbeatMessage);
return true;
};
timer.scheduleAtFixedRate(task, this.heartbeatIntervalMs, this.heartbeatIntervalMs);
}
@Override
public boolean isClosed() {
return closable().isClosed();
}
@Override
public void writeMarshallable(@NotNull WireOut w) {
w.write(() -> "heartbeatTimeoutMs").int64(heartbeatTimeoutMs);
assert heartbeatIntervalMs > 0;
w.write(() -> "heartbeatIntervalMs").int64(heartbeatIntervalMs);
}
@Override
public void onRead(@NotNull WireIn inWire, @NotNull WireOut outWire) {
if (inWire.isEmpty())
return;
inWire.read(() -> "heartbeat").text();
}
@Override
public void close() {
if (connectionMonitor != null)
connectionMonitor.onDisconnected(localIdentifier(), remoteIdentifier(), nc().isAcceptor());
if (closable().isClosed())
return;
lastTimeMessageReceived = Long.MAX_VALUE;
Closeable.closeQuietly(closable());
}
public void onMessageReceived() {
lastTimeMessageReceived = System.currentTimeMillis();
}
private VanillaEventHandler heartbeatCheck() {
return () -> {
if (HeartbeatHandler.this.closable().isClosed())
throw new InvalidEventHandlerException("closed");
boolean hasHeartbeats = hasReceivedHeartbeat();
boolean prev = this.hasHeartbeats.getAndSet(hasHeartbeats);
if (hasHeartbeats != prev) {
if (!hasHeartbeats) {
connectionMonitor.onDisconnected(HeartbeatHandler.this.localIdentifier(),
HeartbeatHandler.this.remoteIdentifier(), nc().isAcceptor());
HeartbeatHandler.this.close();
final Runnable socketReconnector = nc().socketReconnector();
// if we have been terminated then we should not attempt to reconnect
if (nc().terminationEventHandler().isTerminated() && socketReconnector != null)
socketReconnector.run();
throw new InvalidEventHandlerException("closed");
} else
connectionMonitor.onConnected(HeartbeatHandler.this.localIdentifier(),
HeartbeatHandler.this.remoteIdentifier(), nc().isAcceptor());
}
return true;
};
}
/**
* periodically check that messages have been received, ie heartbeats
*/
private void startPeriodicHeartbeatCheck() {
timer.scheduleAtFixedRate(heartbeatCheck(), 0, heartbeatTimeoutMs);
}
/**
* called periodically to check that the heartbeat has been received
*
* @return {@code true} if we have received a heartbeat recently
*/
private boolean hasReceivedHeartbeat() {
long currentTimeMillis = System.currentTimeMillis();
boolean result = lastTimeMessageReceived + heartbeatTimeoutMs >= currentTimeMillis;
if (!result)
Jvm.warn().on(getClass(), Integer.toHexString(hashCode()) + " missed heartbeat, lastTimeMessageReceived=" + lastTimeMessageReceived
+ ", currentTimeMillis=" + currentTimeMillis);
return result;
}
public static class Factory implements Function<ClusterContext, WriteMarshallable>,
Demarshallable {
@UsedViaReflection
private Factory(WireIn w) {
}
public Factory() {
}
@NotNull
@Override
public WriteMarshallable apply(@NotNull ClusterContext clusterContext) {
long heartbeatTimeoutMs = clusterContext.heartbeatTimeoutMs();
long heartbeatIntervalMs = clusterContext.heartbeatIntervalMs();
return heartbeatHandler(heartbeatTimeoutMs, heartbeatIntervalMs,
HeartbeatHandler.class.hashCode());
}
}
}