/** * 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.activemq.transport.discovery.simple; import java.io.IOException; import java.net.URI; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.activemq.command.DiscoveryEvent; import org.apache.activemq.thread.TaskRunnerFactory; import org.apache.activemq.transport.discovery.DiscoveryAgent; import org.apache.activemq.transport.discovery.DiscoveryListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A simple DiscoveryAgent that allows static configuration of the discovered * services. * * */ public class SimpleDiscoveryAgent implements DiscoveryAgent { private final static Logger LOG = LoggerFactory.getLogger(SimpleDiscoveryAgent.class); private long initialReconnectDelay = 1000; private long maxReconnectDelay = 1000 * 30; private long backOffMultiplier = 2; private boolean useExponentialBackOff=true; private int maxReconnectAttempts; private final Object sleepMutex = new Object(); private long minConnectTime = 5000; private DiscoveryListener listener; private String services[] = new String[] {}; private final AtomicBoolean running = new AtomicBoolean(false); private TaskRunnerFactory taskRunner; class SimpleDiscoveryEvent extends DiscoveryEvent { private int connectFailures; private long reconnectDelay = -1; private long connectTime = System.currentTimeMillis(); private final AtomicBoolean failed = new AtomicBoolean(false); public SimpleDiscoveryEvent(String service) { super(service); } public SimpleDiscoveryEvent(SimpleDiscoveryEvent copy) { super(copy); connectFailures = copy.connectFailures; reconnectDelay = copy.reconnectDelay; connectTime = copy.connectTime; failed.set(copy.failed.get()); } @Override public String toString() { return "[" + serviceName + ", failed:" + failed + ", connectionFailures:" + connectFailures + "]"; } } @Override public void setDiscoveryListener(DiscoveryListener listener) { this.listener = listener; } @Override public void registerService(String name) throws IOException { } @Override public void start() throws Exception { taskRunner = new TaskRunnerFactory(); taskRunner.init(); running.set(true); for (int i = 0; i < services.length; i++) { listener.onServiceAdd(new SimpleDiscoveryEvent(services[i])); } } @Override public void stop() throws Exception { running.set(false); if (taskRunner != null) { taskRunner.shutdown(); } // TODO: Should we not remove the services on the listener? synchronized (sleepMutex) { sleepMutex.notifyAll(); } } public String[] getServices() { return services; } public void setServices(String services) { this.services = services.split(","); } public void setServices(String services[]) { this.services = services; } public void setServices(URI services[]) { this.services = new String[services.length]; for (int i = 0; i < services.length; i++) { this.services[i] = services[i].toString(); } } @Override public void serviceFailed(DiscoveryEvent devent) throws IOException { final SimpleDiscoveryEvent sevent = (SimpleDiscoveryEvent)devent; if (running.get() && sevent.failed.compareAndSet(false, true)) { listener.onServiceRemove(sevent); taskRunner.execute(new Runnable() { @Override public void run() { SimpleDiscoveryEvent event = new SimpleDiscoveryEvent(sevent); // We detect a failed connection attempt because the service // fails right away. if (event.connectTime + minConnectTime > System.currentTimeMillis()) { LOG.debug("Failure occurred soon after the discovery event was generated. It will be classified as a connection failure: {}", event); event.connectFailures++; if (maxReconnectAttempts > 0 && event.connectFailures >= maxReconnectAttempts) { LOG.warn("Reconnect attempts exceeded {} tries. Reconnecting has been disabled for: {}", maxReconnectAttempts, event); return; } if (!useExponentialBackOff || event.reconnectDelay == -1) { event.reconnectDelay = initialReconnectDelay; } else { // Exponential increment of reconnect delay. event.reconnectDelay *= backOffMultiplier; if (event.reconnectDelay > maxReconnectDelay) { event.reconnectDelay = maxReconnectDelay; } } doReconnectDelay(event); } else { LOG.trace("Failure occurred to long after the discovery event was generated. " + "It will not be classified as a connection failure: {}", event); event.connectFailures = 0; event.reconnectDelay = initialReconnectDelay; doReconnectDelay(event); } if (!running.get()) { LOG.debug("Reconnecting disabled: stopped"); return; } event.connectTime = System.currentTimeMillis(); event.failed.set(false); listener.onServiceAdd(event); } }, "Simple Discovery Agent"); } } protected void doReconnectDelay(SimpleDiscoveryEvent event) { synchronized (sleepMutex) { try { if (!running.get()) { LOG.debug("Reconnecting disabled: stopped"); return; } LOG.debug("Waiting {}ms before attempting to reconnect.", event.reconnectDelay); sleepMutex.wait(event.reconnectDelay); } catch (InterruptedException ie) { LOG.debug("Reconnecting disabled: ", ie); Thread.currentThread().interrupt(); return; } } } public long getBackOffMultiplier() { return backOffMultiplier; } public void setBackOffMultiplier(long backOffMultiplier) { this.backOffMultiplier = backOffMultiplier; } public long getInitialReconnectDelay() { return initialReconnectDelay; } public void setInitialReconnectDelay(long initialReconnectDelay) { this.initialReconnectDelay = initialReconnectDelay; } public int getMaxReconnectAttempts() { return maxReconnectAttempts; } public void setMaxReconnectAttempts(int maxReconnectAttempts) { this.maxReconnectAttempts = maxReconnectAttempts; } public long getMaxReconnectDelay() { return maxReconnectDelay; } public void setMaxReconnectDelay(long maxReconnectDelay) { this.maxReconnectDelay = maxReconnectDelay; } public long getMinConnectTime() { return minConnectTime; } public void setMinConnectTime(long minConnectTime) { this.minConnectTime = minConnectTime; } public boolean isUseExponentialBackOff() { return useExponentialBackOff; } public void setUseExponentialBackOff(boolean useExponentialBackOff) { this.useExponentialBackOff = useExponentialBackOff; } }