/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.cache.client.internal; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.distributed.internal.ServerLocation; import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID; import org.apache.geode.internal.cache.tier.sockets.ServerQueueStatus; import org.apache.geode.internal.logging.LogService; import org.apache.geode.security.GemFireSecurityException; import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; import java.util.*; /** * A connection source where the list of endpoints is specified explicitly. * * @since GemFire 5.7 * * TODO - the UnusedServerMonitor basically will force the pool to have at least one * connection to each server. Maybe we need to have it create connections that are outside * the pool? * */ public class ExplicitConnectionSourceImpl implements ConnectionSource { private static final Logger logger = LogService.getLogger(); private List serverList; private int nextServerIndex = 0; private int nextQueueIndex = 0; private InternalPool pool; /** * A debug flag, which can be toggled by tests to disable/enable shuffling of the endpoints list */ private boolean DISABLE_SHUFFLING = Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "bridge.disableShufflingOfEndpoints"); public ExplicitConnectionSourceImpl(List/* <InetSocketAddress> */ contacts) { ArrayList serverList = new ArrayList(contacts.size()); for (int i = 0; i < contacts.size(); i++) { InetSocketAddress addr = (InetSocketAddress) contacts.get(i); serverList.add(new ServerLocation(addr.getHostName(), addr.getPort())); } shuffle(serverList); this.serverList = Collections.unmodifiableList(serverList); } @Override public synchronized void start(InternalPool pool) { this.pool = pool; pool.getStats().setInitialContacts(serverList.size()); } @Override public void stop() { // do nothing } @Override public ServerLocation findReplacementServer(ServerLocation currentServer, Set/* <ServerLocation> */ excludedServers) { // at this time we always try to find a server other than currentServer // and if we do return it. Otherwise return null; // so that clients would attempt to keep the same number of connections // to each server but it would be a bit of work. // Plus we need to make sure it would work ok for hardware load balancers. HashSet excludedPlusCurrent = new HashSet(excludedServers); excludedPlusCurrent.add(currentServer); return findServer(excludedPlusCurrent); } @Override public synchronized ServerLocation findServer(Set excludedServers) { if (PoolImpl.TEST_DURABLE_IS_NET_DOWN) { return null; } ServerLocation nextServer; int startIndex = nextServerIndex; do { nextServer = (ServerLocation) serverList.get(nextServerIndex); if (++nextServerIndex >= serverList.size()) { nextServerIndex = 0; } if (!excludedServers.contains(nextServer)) { return nextServer; } } while (nextServerIndex != startIndex); return null; } /** * TODO - this algorithm could be cleaned up. Right now we have to connect to every server in the * system to find where our durable queue lives. */ @Override public synchronized List findServersForQueue(Set excludedServers, int numServers, ClientProxyMembershipID proxyId, boolean findDurableQueue) { if (PoolImpl.TEST_DURABLE_IS_NET_DOWN) { return new ArrayList(); } if (numServers == -1) { numServers = Integer.MAX_VALUE; } if (findDurableQueue && proxyId.isDurable()) { return findDurableQueues(excludedServers, numServers); } else { return pickQueueServers(excludedServers, numServers); } } @Override public boolean isBalanced() { return false; } private List pickQueueServers(Set excludedServers, int numServers) { ArrayList result = new ArrayList(); ServerLocation nextQueue; int startIndex = nextQueueIndex; do { nextQueue = (ServerLocation) serverList.get(nextQueueIndex); if (++nextQueueIndex >= serverList.size()) { nextQueueIndex = 0; } if (!excludedServers.contains(nextQueue)) { result.add(nextQueue); } } while (nextQueueIndex != startIndex && result.size() < numServers); return result; } /** * a "fake" operation which just extracts the queue status from the connection */ private static class HasQueueOp implements Op { public static final HasQueueOp SINGLETON = new HasQueueOp(); public Object attempt(Connection cnx) throws Exception { ServerQueueStatus status = cnx.getQueueStatus(); return status.isNonRedundant() ? Boolean.FALSE : Boolean.TRUE; } @Override public boolean useThreadLocalConnection() { return false; } } private List findDurableQueues(Set excludedServers, int numServers) { ArrayList durableServers = new ArrayList(); ArrayList otherServers = new ArrayList(); logger.debug("ExplicitConnectionSource - looking for durable queue"); for (Iterator itr = serverList.iterator(); itr.hasNext();) { ServerLocation server = (ServerLocation) itr.next(); if (excludedServers.contains(server)) { continue; } // the pool will automatically create a connection to this server // and store it for future use. Boolean hasQueue; try { hasQueue = (Boolean) pool.executeOn(server, HasQueueOp.SINGLETON); } catch (GemFireSecurityException e) { throw e; } catch (Exception e) { if (e.getCause() instanceof GemFireSecurityException) { throw (GemFireSecurityException) e.getCause(); } if (logger.isDebugEnabled()) { logger.debug("Unabled to check for durable queue on server {}: {}", server, e); } continue; } if (hasQueue != null) { if (hasQueue.booleanValue()) { if (logger.isDebugEnabled()) { logger.debug("Durable queue found on {}", server); } durableServers.add(server); } else { if (logger.isDebugEnabled()) { logger.debug("Durable queue was not found on {}", server); } otherServers.add(server); } } } int remainingServers = numServers - durableServers.size(); if (remainingServers > otherServers.size()) { remainingServers = otherServers.size(); } // note, we're always prefering the servers in the beginning of the list // but that's ok because we already shuffled the list in our constructor. if (remainingServers > 0) { durableServers.addAll(otherServers.subList(0, remainingServers)); nextQueueIndex = remainingServers % serverList.size(); } if (logger.isDebugEnabled()) { logger.debug("found {} servers out of {}", durableServers.size(), numServers); } return durableServers; } private void shuffle(List endpoints) { // this check was copied from ConnectionProxyImpl if (endpoints.size() < 2 || DISABLE_SHUFFLING) { /* * It is not safe to shuffle an ArrayList of size 1 java.lang.IndexOutOfBoundsException: * Index: 1, Size: 1 at java.util.ArrayList.RangeCheck(Unknown Source) at * java.util.ArrayList.get(Unknown Source) at java.util.Collections.swap(Unknown Source) at * java.util.Collections.shuffle(Unknown Source) */ return; } Collections.shuffle(endpoints); } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("EndPoints["); synchronized (this) { Iterator it = serverList.iterator(); while (it.hasNext()) { ServerLocation loc = (ServerLocation) it.next(); sb.append(loc.getHostName() + ":" + loc.getPort()); if (it.hasNext()) { sb.append(","); } } } sb.append("]"); return sb.toString(); } @Override public ArrayList<ServerLocation> getAllServers() { ArrayList<ServerLocation> list = new ArrayList<ServerLocation>(); list.addAll(this.serverList); return list; } }