package io.vertx.core.eventbus.impl.clustered;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.EventBusOptions;
import io.vertx.core.eventbus.impl.codecs.PingMessageCodec;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.net.impl.NetClientImpl;
import io.vertx.core.net.impl.ServerID;
import io.vertx.core.spi.metrics.EventBusMetrics;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* @author <a href="http://tfox.org">Tim Fox</a>
*/
class ConnectionHolder {
private static final Logger log = LoggerFactory.getLogger(ConnectionHolder.class);
private static final String PING_ADDRESS = "__vertx_ping";
private final ClusteredEventBus eventBus;
private final NetClient client;
private final ServerID serverID;
private final Vertx vertx;
private final EventBusMetrics metrics;
private Queue<ClusteredMessage> pending;
private NetSocket socket;
private boolean connected;
private long timeoutID = -1;
private long pingTimeoutID = -1;
ConnectionHolder(ClusteredEventBus eventBus, ServerID serverID, EventBusOptions options) {
this.eventBus = eventBus;
this.serverID = serverID;
this.vertx = eventBus.vertx();
this.metrics = eventBus.getMetrics();
NetClientOptions clientOptions = new NetClientOptions(options.toJson());
ClusteredEventBus.setCertOptions(clientOptions, options.getKeyCertOptions());
ClusteredEventBus.setTrustOptions(clientOptions, options.getTrustOptions());
client = new NetClientImpl(eventBus.vertx(), clientOptions, false);
}
synchronized void connect() {
if (connected) {
throw new IllegalStateException("Already connected");
}
client.connect(serverID.port, serverID.host, res -> {
if (res.succeeded()) {
connected(res.result());
} else {
close();
}
});
}
// TODO optimise this (contention on monitor)
synchronized void writeMessage(ClusteredMessage message) {
if (connected) {
Buffer data = message.encodeToWire();
metrics.messageWritten(message.address(), data.length());
socket.write(data);
} else {
if (pending == null) {
pending = new ArrayDeque<>();
}
pending.add(message);
}
}
void close() {
if (timeoutID != -1) {
vertx.cancelTimer(timeoutID);
}
if (pingTimeoutID != -1) {
vertx.cancelTimer(pingTimeoutID);
}
try {
client.close();
} catch (Exception ignore) {
}
// The holder can be null or different if the target server is restarted with same serverid
// before the cleanup for the previous one has been processed
if (eventBus.connections().remove(serverID, this)) {
log.debug("Cluster connection closed: " + serverID + " holder " + this);
}
}
private void schedulePing() {
EventBusOptions options = eventBus.options();
pingTimeoutID = vertx.setTimer(options.getClusterPingInterval(), id1 -> {
// If we don't get a pong back in time we close the connection
timeoutID = vertx.setTimer(options.getClusterPingReplyInterval(), id2 -> {
// Didn't get pong in time - consider connection dead
log.warn("No pong from server " + serverID + " - will consider it dead");
close();
});
ClusteredMessage pingMessage =
new ClusteredMessage<>(serverID, PING_ADDRESS, null, null, null, new PingMessageCodec(), true, eventBus);
Buffer data = pingMessage.encodeToWire();
socket.write(data);
});
}
private synchronized void connected(NetSocket socket) {
this.socket = socket;
connected = true;
socket.exceptionHandler(t -> close());
socket.closeHandler(v -> close());
socket.handler(data -> {
// Got a pong back
vertx.cancelTimer(timeoutID);
schedulePing();
});
// Start a pinger
schedulePing();
for (ClusteredMessage message : pending) {
Buffer data = message.encodeToWire();
metrics.messageWritten(message.address(), data.length());
socket.write(data);
}
pending.clear();
}
}