![JAR search and dependency download from the Maven repository](/logo.png)
io.nats.client.impl.NatsServerPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jnats Show documentation
Show all versions of jnats Show documentation
Client library for working with the NATS messaging system.
The newest version!
// Copyright 2023 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.Options;
import io.nats.client.ServerPool;
import io.nats.client.support.NatsConstants;
import io.nats.client.support.NatsUri;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReentrantLock;
public class NatsServerPool implements ServerPool {
protected final ReentrantLock listLock;
protected List entryList;
protected Options options;
protected int maxConnectAttempts;
protected NatsUri lastConnected;
protected boolean hasSecureServer;
protected String defaultScheme;
public NatsServerPool() {
listLock = new ReentrantLock();
}
/**
* {@inheritDoc}
*/
public void initialize(Options opts) {
// 1. Hold on to options as we need them for settings
options = opts;
// 2. maxConnectAttempts accounts for the first connect attempt and also reconnect attempts
maxConnectAttempts = options.getMaxReconnect() < 0 ? Integer.MAX_VALUE : options.getMaxReconnect() + 1;
// 3. Add all the bootstrap to the server list and prepare list for next
// FYI bootstrap will always have at least the default url
listLock.lock();
try {
entryList = new ArrayList<>();
for (NatsUri nuri : options.getNatsServerUris()) {
// 1. If item is not found in the list being built, add to the list
boolean notAlreadyInList = true;
for (ServerPoolEntry entry : entryList) {
if (nuri.equivalent(entry.nuri)) {
notAlreadyInList = false;
break;
}
}
if (notAlreadyInList) {
if (defaultScheme == null && !nuri.getScheme().equals(NatsConstants.NATS_PROTOCOL)) {
defaultScheme = nuri.getScheme();
}
entryList.add(new ServerPoolEntry(nuri, false));
}
}
// 6. prepare list for next
afterListChanged();
}
finally {
listLock.unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean acceptDiscoveredUrls(List discoveredServers) {
// 1. If ignored discovered servers, don't do anything b/c never want
// anything but the explicit, which is already loaded.
// 2. return false == no new servers discovered
if (options.isIgnoreDiscoveredServers()) {
return false;
}
listLock.lock();
try {
// 2. Build a list for discovered
// - since we'll need the NatsUris later
// - and to have a list to use to prune removed gossiped servers
List discovered = new ArrayList<>();
for (String d : discoveredServers) {
try {
discovered.add(new NatsUri(d, defaultScheme));
} catch (URISyntaxException ignore) {
// should never actually happen
}
}
// 3. Start a new server list, loading in current order from the current list, and keeping
// - the last connected
// - all non-gossiped
// - any found in the new discovered list
// - for any new discovered, we also remove them from
// that list so step there are no dupes for step #4
// - This also maintains the Srv state of an already known discovered
List newEntryList = new ArrayList<>();
for (ServerPoolEntry entry : entryList) {
int ix = findEquivalent(discovered, entry.nuri);
if (ix != -1 || entry.nuri.equals(lastConnected) || !entry.isGossiped) {
newEntryList.add(entry);
if (ix != -1) {
discovered.remove(ix);
}
}
}
// 4. Add all left over from the new discovered list
boolean discoveryContainedUnknowns = false;
if (!discovered.isEmpty()) {
discoveryContainedUnknowns = true;
for (NatsUri d : discovered) {
newEntryList.add(new ServerPoolEntry(d, true));
}
}
// 5. replace the list with the new one
entryList = newEntryList;
// 6. prepare list for next
afterListChanged();
// 7.
return discoveryContainedUnknowns;
}
finally {
listLock.unlock();
}
}
protected void afterListChanged() {
// 1. randomize if needed and allowed
if (entryList.size() > 1 && !options.isNoRandomize()) {
Collections.shuffle(entryList, ThreadLocalRandom.current());
}
// 2. calculate hasSecureServer and find the index of lastConnected
hasSecureServer = false;
int lastConnectedIx = -1;
for (int ix = 0; ix < entryList.size(); ix++) {
NatsUri nuri = entryList.get(ix).nuri;
hasSecureServer |= nuri.isSecure();
if (nuri.equals(lastConnected)) {
lastConnectedIx = ix;
}
}
// C. put the last connected server at the end of the list
if (lastConnectedIx != -1) {
entryList.add(entryList.remove(lastConnectedIx));
}
}
@Override
public NatsUri peekNextServer() {
listLock.lock();
try {
return entryList.isEmpty() ? null : entryList.get(0).nuri;
}
finally {
listLock.unlock();
}
}
@Override
public NatsUri nextServer() {
// 0. The list is already managed for qualified by connectFailed
// 1. Get the first item in the list, update it's time, add back to the end of list
listLock.lock();
try {
if (!entryList.isEmpty()) {
ServerPoolEntry entry = entryList.remove(0);
entry.lastAttempt = System.currentTimeMillis();
entryList.add(entry);
return entry.nuri;
}
return null;
}
finally {
listLock.unlock();
}
}
@Override
public List resolveHostToIps(String host) {
// 1. if options.isNoResolveHostnames(), return empty list
if (options.isNoResolveHostnames()) {
return null;
}
// 2. else, try to resolve the hostname, adding results to list
List results = new ArrayList<>();
try {
InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress a : addresses) {
results.add(a.getHostAddress());
}
}
catch (UnknownHostException ignore) {
// A user might have supplied a bad host, but the server shouldn't.
// Either way, nothing much we can do.
}
// 3. no results, return null.
if (results.isEmpty()) {
return null;
}
// 4. if results has more than 1 and allowed to randomize, shuffle the list
if (results.size() > 1 && !options.isNoRandomize()) {
Collections.shuffle(results, ThreadLocalRandom.current());
}
return results;
}
@Override
public void connectSucceeded(NatsUri nuri) {
// 1. Work from the end because nextServer moved the one being tried to the end
// 2. If we find the server in the list...
// 2.1. remember it and
// 2.2. reset failed attempts
listLock.lock();
try {
for (int x = entryList.size() - 1; x >= 0 ; x--) {
ServerPoolEntry entry = entryList.get(x);
if (entry.nuri.equals(nuri)) {
lastConnected = nuri;
entry.failedAttempts = 0;
return;
}
}
}
finally {
listLock.unlock();
}
}
@Override
public void connectFailed(NatsUri nuri) {
// 1. Work from the end because nextServer moved the one being tried to the end
// 2. If we find the server in the list...
// 2.1. increment failed attempts
// 2.2. if failed attempts reaches max, remove it from the list
listLock.lock();
try {
for (int x = entryList.size() - 1; x >= 0 ; x--) {
ServerPoolEntry entry = entryList.get(x);
if (entry.nuri.equals(nuri)) {
if (++entry.failedAttempts >= maxConnectAttempts) {
entryList.remove(x);
}
return;
}
}
}
finally {
listLock.unlock();
}
}
@Override
public List getServerList() {
listLock.lock();
try {
List list = new ArrayList<>();
for (ServerPoolEntry entry : entryList) {
list.add(entry.nuri.toString());
}
return list;
}
finally {
listLock.unlock();
}
}
@Override
public boolean hasSecureServer() {
return hasSecureServer;
}
protected int findEquivalent(List list, NatsUri toFind) {
for (int i = 0; i < list.size(); i++) {
NatsUri nuri = list.get(i);
if (nuri.equivalent(toFind)) {
return i;
}
}
return -1;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy