/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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 com.hazelcast.test.mocknetwork; import com.hazelcast.core.Member; import com.hazelcast.instance.Node; import com.hazelcast.instance.NodeState; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Address; import com.hazelcast.nio.Connection; import com.hazelcast.nio.ConnectionListener; import com.hazelcast.nio.ConnectionManager; import com.hazelcast.nio.IOService; import com.hazelcast.nio.Packet; import com.hazelcast.spi.ExecutionService; import com.hazelcast.util.executor.StripedRunnable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static com.hazelcast.test.HazelcastTestSupport.suspectMember; public class MockConnectionManager implements ConnectionManager { private static final int RETRY_NUMBER = 5; private static final int DELAY_FACTOR = 100; private final ConcurrentMap<Address, Connection> mapConnections = new ConcurrentHashMap<Address, Connection>(10); private final TestNodeRegistry registry; private final Node node; private final Set<ConnectionListener> connectionListeners = new CopyOnWriteArraySet<ConnectionListener>(); private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(4); private final IOService ioService; private final ILogger logger; private volatile boolean live; public MockConnectionManager(IOService ioService, Node node, TestNodeRegistry registry) { this.ioService = ioService; this.registry = registry; this.node = node; this.logger = ioService.getLoggingService().getLogger(MockConnectionManager.class); } @Override public Connection getConnection(Address address) { return mapConnections.get(address); } @Override public Connection getOrConnect(Address address) { Connection conn = mapConnections.get(address); if (conn != null && conn.isAlive()) { return conn; } if (!live) { return null; } Node targetNode = registry.getNode(address); if (targetNode == null || isTargetLeft(targetNode)) { suspectAddress(address); return null; } return createConnection(targetNode); } private void suspectAddress(final Address address) { // see NodeIOService#removeEndpoint() node.getNodeEngine().getExecutionService().execute(ExecutionService.IO_EXECUTOR, new Runnable() { @Override public void run() { node.getClusterService().suspectAddressIfNotConnected(address); } }); } static boolean isTargetLeft(Node targetNode) { return !targetNode.isRunning() && !targetNode.getClusterService().isJoined(); } private synchronized Connection createConnection(Node targetNode) { if (!live) { throw new IllegalStateException("connection manager is not live!"); } Address target = targetNode.getThisAddress(); MockConnection thisConnection = new MockConnection(target, node.getThisAddress(), node.getNodeEngine()); MockConnection remoteConnection = new MockConnection(node.getThisAddress(), target, targetNode.getNodeEngine()); remoteConnection.localConnection = thisConnection; thisConnection.localConnection = remoteConnection; mapConnections.put(target, remoteConnection); logger.info("Created connection to endpoint: " + target + ", connection: " + remoteConnection); return remoteConnection; } @Override public Connection getOrConnect(Address address, boolean silent) { return getOrConnect(address); } @Override public synchronized void start() { logger.fine("Starting connection manager"); live = true; } @Override public synchronized void stop() { logger.fine("Stopping connection manager"); live = false; for (Connection connection : mapConnections.values()) { connection.close(null, null); } mapConnections.clear(); final Member localMember = node.getLocalMember(); final Address thisAddress = localMember.getAddress(); for (Address address : registry.getAddresses()) { if (address.equals(thisAddress)) { continue; } Node otherNode = registry.getNode(address); if (otherNode != null && otherNode.getState() != NodeState.SHUT_DOWN) { logger.fine(otherNode.getThisAddress() + " is instructed to suspect from " + thisAddress); try { suspectMember(otherNode, node, "Connection manager is stopped on " + localMember); } catch (Throwable e) { ILogger otherLogger = otherNode.getLogger(MockConnectionManager.class); otherLogger.warning("While removing " + thisAddress, e); } } } } @Override public synchronized void shutdown() { stop(); } @Override public synchronized boolean registerConnection(final Address remoteEndpoint, final Connection connection) { if (!live) { throw new IllegalStateException("connection manager is not live!"); } if (!connection.isAlive()) { return false; } mapConnections.put(remoteEndpoint, connection); ioService.getEventService().executeEventCallback(new StripedRunnable() { @Override public void run() { for (ConnectionListener listener : connectionListeners) { listener.connectionAdded(connection); } } @Override public int getKey() { return remoteEndpoint.hashCode(); } }); return true; } @Override public void addConnectionListener(ConnectionListener connectionListener) { connectionListeners.add(connectionListener); } @Override public void onConnectionClose(final Connection connection) { final Address endPoint = connection.getEndPoint(); if (mapConnections.remove(endPoint, connection)) { logger.info("Removed connection to endpoint: " + endPoint + ", connection: " + connection); ioService.getEventService().executeEventCallback(new StripedRunnable() { @Override public void run() { for (ConnectionListener listener : connectionListeners) { listener.connectionRemoved(connection); } } @Override public int getKey() { return endPoint.hashCode(); } }); } } @Override public int getActiveConnectionCount() { return 0; } @Override public int getCurrentClientConnections() { return 0; } @Override public int getConnectionCount() { return 0; } @Override public int getAllTextConnections() { return 0; } @Override public boolean transmit(Packet packet, Connection connection) { return (connection != null && connection.write(packet)); } /** * Retries sending packet maximum 5 times until connection to target becomes available. */ @Override public boolean transmit(Packet packet, Address target) { return send(packet, target, null); } private boolean send(Packet packet, Address target, SendTask sendTask) { Connection connection = getConnection(target); if (connection != null) { return transmit(packet, connection); } if (sendTask == null) { sendTask = new SendTask(packet, target); } int retries = sendTask.retries; if (retries < RETRY_NUMBER && ioService.isActive()) { getOrConnect(target, true); // TODO: Caution: may break the order guarantee of the packets sent from the same thread! scheduler.schedule(sendTask, (retries + 1) * DELAY_FACTOR, TimeUnit.MILLISECONDS); return true; } return false; } private final class SendTask implements Runnable { private final Packet packet; private final Address target; private volatile int retries; private SendTask(Packet packet, Address target) { this.packet = packet; this.target = target; } @SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "single-writer, many-reader") @Override public void run() { retries++; if (logger.isFinestEnabled()) { logger.finest("Retrying[" + retries + "] packet send operation to: " + target); } send(packet, target, this); } } }