All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rocks.xmpp.core.session.ReconnectionManager Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2016 Christian Schudt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package rocks.xmpp.core.session;

import rocks.xmpp.core.XmppException;
import rocks.xmpp.core.stream.StreamErrorException;
import rocks.xmpp.core.stream.model.errors.Condition;
import rocks.xmpp.util.XmppUtils;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.logging.Level;
import java.util.logging.Logger;

import static rocks.xmpp.core.session.ReconnectionStrategy.onSystemShutdownFirstOrElseSecond;
import static rocks.xmpp.core.session.ReconnectionStrategy.truncatedBinaryExponentialBackoffStrategy;

/**
 * If the connection goes down, this class automatically reconnects, if the user was authenticated.
 * 

* Reconnection is not performed, if the user got disconnected due to an {@code } stream error. *

* The default reconnection strategy is a so called truncated binary exponential back off (as proposed by the XMPP specification), * which means that the first reconnection attempt is performed X seconds after the disconnect, where X is between 0 and 60.
* The second attempt chooses a random number between 0 and 180.
* The third attempt chooses a random number between 0 and 420.
* The forth attempt chooses a random number between 0 and 900.
* The fifth attempt chooses a random number between 0 and 1860.
*

* Generally speaking it is 2^attempt * 60 seconds. *

* You can also {@linkplain #setReconnectionStrategy(ReconnectionStrategy) set} your own reconnection strategy. *

*

* Use {@link #getNextReconnectionAttempt()} if you want to find out, when the next reconnection attempt will happen. *

* This class is unconditionally thread-safe. * @deprecated The only useful API here was {@link #setReconnectionStrategy(ReconnectionStrategy)}, use {@link rocks.xmpp.core.session.XmppSessionConfiguration.Builder#reconnectionStrategy(ReconnectionStrategy)} instead. * @author Christian Schudt * @see 3.3. Reconnection */ @Deprecated public final class ReconnectionManager extends Manager { private static final Logger logger = Logger.getLogger(ReconnectionManager.class.getName()); private final ScheduledExecutorService scheduledExecutorService; private ReconnectionStrategy reconnectionStrategy; private ScheduledFuture scheduledReconnectingInterval; private Instant nextReconnectionAttempt; private ReconnectionManager(final XmppSession xmppSession) { super(xmppSession, false); ReconnectionStrategy configuredStrategy = xmppSession.getConfiguration().getReconnectionStrategy(); if (configuredStrategy != null) { this.reconnectionStrategy = configuredStrategy; } else { this.reconnectionStrategy = onSystemShutdownFirstOrElseSecond( // on first attempt: 0-60 seconds (2^1-1 * 60) // on second attempt: 0-180 seconds (2^2-1 * 60) // on third attempt: 0-420 seconds (2^3-1 * 60) // on fourth attempt: 0-900 seconds (2^4-1 * 60) // -> max. 15 minutes truncatedBinaryExponentialBackoffStrategy(60, 4), // on first attempt: 0-10 seconds (2^1-1 * 10) // on second attempt: 0-30 seconds (2^2-1 * 10) // on third attempt: 0-70 seconds (2^3-1 * 10) // on fourth attempt: 0-150 seconds (2^4-1 * 10) // on fifth attempt: 0-310 seconds (2^5-1 * 10) // -> max. ~ 5 minutes truncatedBinaryExponentialBackoffStrategy(10, 5)); } scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(XmppUtils.createNamedThreadFactory("XMPP Reconnection Thread")); } @Override protected final void initialize() { xmppSession.addSessionStatusListener(e -> { switch (e.getStatus()) { case DISCONNECTED: // Reconnect if we were connected or logged in and an exception has occurred, that is not a stream error. if (e.getOldStatus() == XmppSession.Status.AUTHENTICATED) { XmppUtils.notifyEventListeners(xmppSession.connectionListeners, new ConnectionEvent(xmppSession, ConnectionEvent.Type.DISCONNECTED, e.getThrowable(), Duration.ZERO)); if (reconnectionStrategy.mayReconnect(0, e.getThrowable())) { scheduleReconnection(0, e.getThrowable()); } } break; case CONNECTED: cancel(); break; case CLOSED: cancel(); scheduledExecutorService.shutdown(); break; } }); } /** * Cancels the next reconnection attempt. */ private synchronized void cancel() { nextReconnectionAttempt = null; if (scheduledReconnectingInterval != null) { // Cancel the scheduled timer. scheduledReconnectingInterval.cancel(false); } } private synchronized void scheduleReconnection(final int attempt, Throwable throwable) { if (isEnabled()) { Duration duration = reconnectionStrategy.getNextReconnectionAttempt(attempt, throwable); if (attempt == 0) { logger.log(Level.FINE, "Disconnect detected. Next reconnection attempt in {0} seconds.", duration.getSeconds()); } else { logger.log(Level.FINE, "Still disconnected after {0} retries. Next reconnection attempt in {1} seconds.", new Object[]{attempt, duration.getSeconds()}); } nextReconnectionAttempt = Instant.now().plus(duration); scheduledReconnectingInterval = scheduledExecutorService.scheduleAtFixedRate(() -> { Duration remainingDuration = Duration.between(Instant.now(), nextReconnectionAttempt); if (!remainingDuration.isNegative()) { XmppUtils.notifyEventListeners(xmppSession.connectionListeners, new ConnectionEvent(xmppSession, ConnectionEvent.Type.RECONNECTION_PENDING, throwable, remainingDuration)); } else { synchronized (this) { scheduledReconnectingInterval.cancel(false); } try { xmppSession.connect(); logger.log(Level.FINE, "Reconnection successful."); } catch (XmppException e) { logger.log(Level.FINE, "Reconnection failed.", e); XmppUtils.notifyEventListeners(xmppSession.connectionListeners, new ConnectionEvent(xmppSession, ConnectionEvent.Type.RECONNECTION_FAILED, e, Duration.ZERO)); scheduleReconnection(attempt + 1, e); } } }, 0, 1, TimeUnit.SECONDS); } } /** * Gets the reconnection strategy. * * @return The reconnection strategy. */ public final synchronized ReconnectionStrategy getReconnectionStrategy() { return reconnectionStrategy; } /** * Sets the reconnection strategy. * * @param reconnectionStrategy The reconnection strategy. * @deprecated Use {@link rocks.xmpp.core.session.XmppSessionConfiguration.Builder#reconnectionStrategy(ReconnectionStrategy)} */ @Deprecated public final synchronized void setReconnectionStrategy(ReconnectionStrategy reconnectionStrategy) { this.reconnectionStrategy = reconnectionStrategy; } /** * Gets the date of the next reconnection attempt. * * @return The next reconnection attempt or null if there is none. */ public final synchronized Instant getNextReconnectionAttempt() { return nextReconnectionAttempt; } @Override protected final void onDisable() { super.onDisable(); cancel(); } /** * A predicate which returns true as soon a system-shutdown stream error has occurred. */ static final class SystemShutdownPredicate implements BiPredicate { private boolean systemShutdown; @Override public final boolean test(Integer attempt, Throwable cause) { if (!systemShutdown || attempt == 0) { systemShutdown = cause instanceof StreamErrorException && ((StreamErrorException) cause).getCondition() == Condition.SYSTEM_SHUTDOWN; } return systemShutdown; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy