/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.client; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.voltcore.logging.VoltLogger; /** * This listener allows to reconnect to a single server with retry after connection loss by * running a daemon thread that retries connection until success with a simple limited exponential backoff. */ public class ReconnectStatusListener extends ClientStatusListenerExt { private static final VoltLogger LOG = new VoltLogger("HOST"); private final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "Retry Connection"); thread.setDaemon(true); return thread; } }); private final Client m_client; private final long m_initialRetryIntervalMS; private final long m_maxRetryIntervalMS; private final AtomicBoolean m_shouldContinue = new AtomicBoolean(true); protected ReconnectStatusListener(Client client, long initialRetryIntervalMS, long maxRetryIntervalMS) { if (initialRetryIntervalMS < 1) { throw new IllegalArgumentException("initial connection retry interval must be greater than 0, " + initialRetryIntervalMS + " was specified"); } if (maxRetryIntervalMS < 1) { throw new IllegalArgumentException("max connection retry interval must be greater than 0, " + maxRetryIntervalMS + " was specified"); } if (maxRetryIntervalMS < initialRetryIntervalMS) { throw new IllegalArgumentException("max connection retry interval can't be less than initial connection retry interval"); } this.m_client = client; this.m_initialRetryIntervalMS = initialRetryIntervalMS; this.m_maxRetryIntervalMS = maxRetryIntervalMS; } @Override public void connectionLost(final String hostname, final int port, int connectionsLeft, DisconnectCause cause) { LOG.warn(String.format("Connection to VoltDB node at: %s:%d was lost.", hostname, port)); executor.execute(new Runnable() { @Override public void run() { connectToOneServerWithRetry(hostname, port); } }); } /** * Interrupt and shut down the while(true) loop that tries * to reconnect to a server. */ public void close() { m_shouldContinue.set(false); executor.shutdownNow(); try { executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException ignored) {} } /** * Connect to a single server with retry. Limited exponential backoff. * No timeout. This will run until the process is killed or interrupted/closed * if it's not able to connect. * * @param hostname host name * @param port port */ private void connectToOneServerWithRetry(String hostname, int port) { long sleep = m_initialRetryIntervalMS; while (m_shouldContinue.get()) { try { m_client.createConnection(hostname, port); LOG.info(String.format("Connected to VoltDB node at %s:%d.", hostname, port)); break; } catch (Exception e) { LOG.warn(String.format("Connection to VoltDB node at %s:%d failed - retrying in %d second(s).", hostname, port, TimeUnit.MILLISECONDS.toSeconds(sleep))); try { Thread.sleep(sleep); } catch (Exception ignored) { } if (sleep < m_maxRetryIntervalMS) { sleep = Math.min(sleep + sleep, m_maxRetryIntervalMS); } } } } }