Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
// Copyright 2015-2018 The NATS Authors
// 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 io.nats.client.impl;
import io.nats.client.*;
import io.nats.client.ConnectionListener.Events;
import io.nats.client.api.ServerInfo;
import io.nats.client.support.ByteArrayBuilder;
import io.nats.client.support.NatsRequestCompletableFuture;
import io.nats.client.support.NatsUri;
import io.nats.client.support.Validator;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import static io.nats.client.support.NatsConstants.*;
import static io.nats.client.support.NatsRequestCompletableFuture.CancelAction;
import static io.nats.client.support.Validator.*;
import static java.nio.charset.StandardCharsets.UTF_8;
class NatsConnection implements Connection {
public static final double NANOS_PER_SECOND = 1_000_000_000.0;
private final Options options;
private final StatisticsCollector statistics;
private boolean connecting; // you can only connect in one thread
private boolean disconnecting; // you can only disconnect in one thread
private boolean closing; // respect a close call regardless
private Exception exceptionDuringConnectChange; // exception occurred in another thread while dis/connecting
private final ReentrantLock closeSocketLock;
private Status status;
private final ReentrantLock statusLock;
private final Condition statusChanged;
private CompletableFuture dataPortFuture;
private DataPort dataPort;
private NatsUri currentServer;
private CompletableFuture reconnectWaiter;
private final HashMap serverAuthErrors;
private NatsConnectionReader reader;
private NatsConnectionWriter writer;
private final AtomicReference serverInfo;
private final Map subscribers;
private final Map dispatchers; // use a concurrent map so we get more consistent iteration behavior
private final Collection connectionListeners;
private final Map responsesAwaiting;
private final Map responsesRespondedTo;
private final ConcurrentLinkedDeque> pongQueue;
private final String mainInbox;
private final AtomicReference inboxDispatcher;
private final ReentrantLock inboxDispatcherLock;
private Timer timer;
private final AtomicBoolean needPing;
private final AtomicLong nextSid;
private final NUID nuid;
private final AtomicReference connectError;
private final AtomicReference lastError;
private final AtomicReference> draining;
private final AtomicBoolean blockPublishForDrain;
private final AtomicBoolean tryingToConnect;
private final ExecutorService callbackRunner;
private final ExecutorService executor;
private final ExecutorService connectExecutor;
private final boolean advancedTracking;
private final ServerPool serverPool;
private final DispatcherFactory dispatcherFactory;
final CancelAction cancelAction;
private final boolean trace;
private final TimeTraceLogger timeTraceLogger;
NatsConnection(Options options) {
trace = options.isTraceConnection();
timeTraceLogger = options.getTimeTraceLogger();
timeTraceLogger.trace("creating connection object");
this.options = options;
advancedTracking = options.isTrackAdvancedStats();
this.statistics = options.getStatisticsCollector() == null ? new NatsStatistics() : options.getStatisticsCollector();
this.statistics.setAdvancedTracking(advancedTracking);
this.closeSocketLock = new ReentrantLock();
this.statusLock = new ReentrantLock();
this.statusChanged = this.statusLock.newCondition();
this.status = Status.DISCONNECTED;
this.reconnectWaiter = new CompletableFuture<>();
this.reconnectWaiter.complete(Boolean.TRUE);
this.connectionListeners = ConcurrentHashMap.newKeySet();
if (options.getConnectionListener() != null) {
addConnectionListener(options.getConnectionListener());
}
this.dispatchers = new ConcurrentHashMap<>();
this.subscribers = new ConcurrentHashMap<>();
this.responsesAwaiting = new ConcurrentHashMap<>();
this.responsesRespondedTo = new ConcurrentHashMap<>();
this.serverAuthErrors = new HashMap<>();
this.nextSid = new AtomicLong(1);
timeTraceLogger.trace("creating NUID");
this.nuid = new NUID();
this.mainInbox = createInbox() + ".*";
this.lastError = new AtomicReference<>();
this.connectError = new AtomicReference<>();
this.serverInfo = new AtomicReference<>();
this.inboxDispatcher = new AtomicReference<>();
this.inboxDispatcherLock = new ReentrantLock();
this.pongQueue = new ConcurrentLinkedDeque<>();
this.draining = new AtomicReference<>();
this.blockPublishForDrain = new AtomicBoolean();
this.tryingToConnect = new AtomicBoolean();
timeTraceLogger.trace("creating executors");
this.executor = options.getExecutor();
this.callbackRunner = Executors.newSingleThreadExecutor();
this.connectExecutor = Executors.newSingleThreadExecutor();
timeTraceLogger.trace("creating reader and writer");
this.reader = new NatsConnectionReader(this);
this.writer = new NatsConnectionWriter(this, null);
this.needPing = new AtomicBoolean(true);
serverPool = options.getServerPool() == null ? new NatsServerPool() : options.getServerPool();
serverPool.initialize(options);
dispatcherFactory = options.getDispatcherFactory() == null ? new DispatcherFactory() : options.getDispatcherFactory();
cancelAction = options.isReportNoResponders() ? CancelAction.REPORT : CancelAction.CANCEL;
timeTraceLogger.trace("connection object created");
}
// Connect is only called after creation
void connect(boolean reconnectOnConnect) throws InterruptedException, IOException {
if (!tryingToConnect.get()) {
try {
tryingToConnect.set(true);
connectImpl(reconnectOnConnect);
}
finally {
tryingToConnect.set(false);
}
}
}
void connectImpl(boolean reconnectOnConnect) throws InterruptedException, IOException {
if (options.getServers().isEmpty()) {
throw new IllegalArgumentException("No servers provided in options");
}
boolean trace = options.isTraceConnection();
long start = System.nanoTime();
this.lastError.set("");
timeTraceLogger.trace("starting connect loop");
Set failList = new HashSet<>();
boolean keepGoing = true;
NatsUri first = null;
NatsUri cur;
while (keepGoing && (cur = serverPool.peekNextServer()) != null) {
if (first == null) {
first = cur;
}
else if (cur.equals(first)) {
break; // connect only goes through loop once
}
serverPool.nextServer(); // b/c we only peeked.
// let server pool resolve hostnames, then loop through resolved
List resolvedList = resolveHost(cur);
for (NatsUri resolved : resolvedList) {
if (isClosed()) {
keepGoing = false;
break;
}
connectError.set(""); // new on each attempt
timeTraceLogger.trace("setting status to connecting");
updateStatus(Status.CONNECTING);
timeTraceLogger.trace("trying to connect to %s", cur);
tryToConnect(cur, resolved, System.nanoTime());
if (isConnected()) {
serverPool.connectSucceeded(cur);
keepGoing = false;
break;
}
timeTraceLogger.trace("setting status to disconnected");
updateStatus(Status.DISCONNECTED);
failList.add(cur);
serverPool.connectFailed(cur);
String err = connectError.get();
if (this.isAuthenticationError(err)) {
this.serverAuthErrors.put(resolved, err);
}
}
}
if (!isConnected() && !isClosed()) {
if (reconnectOnConnect) {
timeTraceLogger.trace("trying to reconnect on connect");
reconnectImpl(); // call the impl here otherwise the tryingToConnect guard will block the behavior
}
else {
timeTraceLogger.trace("connection failed, closing to cleanup");
close();
String err = connectError.get();
if (this.isAuthenticationError(err)) {
throw new AuthenticationException("Authentication error connecting to NATS server: " + err);
}
throw new IOException("Unable to connect to NATS servers: " + failList);
}
}
else if (trace) {
long end = System.nanoTime();
double seconds = ((double) (end - start)) / NANOS_PER_SECOND;
timeTraceLogger.trace("connect complete in %.3f seconds", seconds);
}
}
@Override
public void forceReconnect() throws IOException, InterruptedException {
forceReconnect(null);
}
@Override
public void forceReconnect(ForceReconnectOptions options) throws IOException, InterruptedException {
if (!tryingToConnect.get()) {
try {
tryingToConnect.set(true);
forceReconnectImpl(options);
}
finally {
tryingToConnect.set(false);
}
}
}
void forceReconnectImpl(ForceReconnectOptions options) throws InterruptedException {
if (options != null && options.getFlushWait() != null) {
try {
flush(options.getFlushWait());
}
catch (TimeoutException e) {
// ignore, don't care, too bad;
}
}
closeSocketLock.lock();
try {
updateStatus(Status.DISCONNECTED);
// Close and reset the current data port and future
if (dataPortFuture != null) {
dataPortFuture.cancel(true);
dataPortFuture = null;
}
// close the data port as a task so as not to block reconnect
if (dataPort != null) {
final DataPort closeMe = dataPort;
dataPort = null;
executor.submit(() -> {
try {
if (options != null && options.isForceClose()) {
closeMe.forceClose();
}
else {
closeMe.close();
}
}
catch (IOException ignore) {
}
});
}
// stop i/o
try {
this.reader.stop(false).get(100, TimeUnit.MILLISECONDS);
}
catch (Exception ex) {
processException(ex);
}
try {
this.writer.stop().get(100, TimeUnit.MILLISECONDS);
}
catch (Exception ex) {
processException(ex);
}
// new reader/writer
reader = new NatsConnectionReader(this);
writer = new NatsConnectionWriter(this, writer);
}
finally {
closeSocketLock.unlock();
}
// calling connect just starts like a new connection versus reconnect
// but we have to manually resubscribe like reconnect once it is connected
reconnectImpl();
writer.setReconnectMode(false);
}
void reconnect() throws InterruptedException {
if (!tryingToConnect.get()) {
try {
tryingToConnect.set(true);
reconnectImpl();
}
finally {
tryingToConnect.set(false);
}
}
}
// Reconnect can only be called when the connection is disconnected
void reconnectImpl() throws InterruptedException {
if (isClosed()) {
return;
}
if (options.getMaxReconnect() == 0) {
this.close();
return;
}
writer.setReconnectMode(true);
if (!isConnected() && !isClosed() && !this.isClosing()) {
boolean keepGoing = true;
int totalRounds = 0;
NatsUri first = null;
NatsUri cur;
while (keepGoing && (cur = serverPool.nextServer()) != null) {
if (first == null) {
first = cur;
}
else if (first.equals(cur)) {
// went around the pool an entire time
invokeReconnectDelayHandler(++totalRounds);
}
// let server list provider resolve hostnames
// then loop through resolved
List resolvedList = resolveHost(cur);
for (NatsUri resolved : resolvedList) {
if (isClosed()) {
keepGoing = false;
break;
}
connectError.set(""); // reset on each loop
if (isDisconnectingOrClosed() || this.isClosing()) {
keepGoing = false;
break;
}
updateStatus(Status.RECONNECTING);
timeTraceLogger.trace("reconnecting to server %s", cur);
tryToConnect(cur, resolved, System.nanoTime());
if (isConnected()) {
serverPool.connectSucceeded(cur);
statistics.incrementReconnects();
keepGoing = false;
break;
}
serverPool.connectFailed(cur);
String err = connectError.get();
if (this.isAuthenticationError(err)) {
if (err.equals(this.serverAuthErrors.get(resolved))) {
keepGoing = false; // double auth error
break;
}
serverAuthErrors.put(resolved, err);
}
}
}
} // end-main-loop
if (!isConnected()) {
this.close();
return;
}
this.subscribers.forEach((sid, sub) -> {
if (sub.getDispatcher() == null && !sub.isDraining()) {
sendSubscriptionMessage(sub.getSID(), sub.getSubject(), sub.getQueueName(), true);
}
});
this.dispatchers.forEach((nuid, d) -> {
if (!d.isDraining()) {
d.resendSubscriptions();
}
});
try {
this.flush(this.options.getConnectionTimeout());
} catch (Exception exp) {
this.processException(exp);
}
processConnectionEvent(Events.RESUBSCRIBED);
// When the flush returns we are done sending internal messages,
// so we can switch to the non-reconnect queue
this.writer.setReconnectMode(false);
}
long timeCheck(long endNanos, String message) throws TimeoutException {
long remaining = endNanos - System.nanoTime();
if (trace) {
traceTimeCheck(message, remaining);
}
if (remaining < 0) {
throw new TimeoutException("connection timed out");
}
return remaining;
}
void traceTimeCheck(String message, long remaining) {
if (remaining < 0) {
if (remaining > -1_000_000) { // less than -1 ms
timeTraceLogger.trace(message + String.format(", %d (ns) beyond timeout", -remaining));
}
else if (remaining > -1_000_000_000) { // less than -1 second
long ms = -remaining / 1_000_000;
timeTraceLogger.trace(message + String.format(", %d (ms) beyond timeout", ms));
}
else {
double seconds = ((double)-remaining) / 1_000_000_000.0;
timeTraceLogger.trace(message + String.format(", %.3f (s) beyond timeout", seconds));
}
}
else if (remaining < 1_000_000) {
timeTraceLogger.trace(message + String.format(", %d (ns) remaining", remaining));
}
else if (remaining < 1_000_000_000) {
long ms = remaining / 1_000_000;
timeTraceLogger.trace(message + String.format(", %d (ms) remaining", ms));
}
else {
double seconds = ((double) remaining) / 1_000_000_000.0;
timeTraceLogger.trace(message + String.format(", %.3f (s) remaining", seconds));
}
}
// is called from reconnect and connect
// will wait for any previous attempt to complete, using the reader.stop and
// writer.stop
void tryToConnect(NatsUri cur, NatsUri resolved, long now) {
currentServer = null;
try {
Duration connectTimeout = options.getConnectionTimeout();
boolean trace = options.isTraceConnection();
long end = now + connectTimeout.toNanos();
timeCheck(end, "starting connection attempt");
statusLock.lock();
try {
if (this.connecting) {
return;
}
this.connecting = true;
statusChanged.signalAll();
} finally {
statusLock.unlock();
}
// Create a new future for the dataport, the reader/writer will use this
// to wait for the connect/failure.
this.dataPortFuture = new CompletableFuture<>();
// Make sure the reader and writer are stopped
long timeoutNanos = timeCheck(end, "waiting for reader");
if (reader.isRunning()) {
this.reader.stop().get(timeoutNanos, TimeUnit.NANOSECONDS);
}
timeoutNanos = timeCheck(end, "waiting for writer");
if (writer.isRunning()) {
this.writer.stop().get(timeoutNanos, TimeUnit.NANOSECONDS);
}
timeCheck(end, "cleaning pong queue");
cleanUpPongQueue();
timeoutNanos = timeCheck(end, "connecting data port");
DataPort newDataPort = this.options.buildDataPort();
newDataPort.connect(resolved.toString(), this, timeoutNanos);
// Notify any threads waiting on the sockets
this.dataPort = newDataPort;
this.dataPortFuture.complete(this.dataPort);
// Wait for the INFO message manually
// all other traffic will use the reader and writer
// TLS First, don't read info until after upgrade
Callable