mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-26 14:02:06 +01:00
Rework and redesign ReconnectionManager
Move reconnection manager setting from ConnectionConfiguration into ReconnectionManager. Apply Manager pattern to ReconnectionManager.
This commit is contained in:
parent
b5328d38fa
commit
9a6d042652
2 changed files with 216 additions and 164 deletions
|
@ -68,9 +68,6 @@ public class ConnectionConfiguration implements Cloneable {
|
||||||
|
|
||||||
private boolean debuggerEnabled = SmackConfiguration.DEBUG_ENABLED;
|
private boolean debuggerEnabled = SmackConfiguration.DEBUG_ENABLED;
|
||||||
|
|
||||||
// Flag that indicates if a reconnection should be attempted when abruptly disconnected
|
|
||||||
private boolean reconnectionAllowed = true;
|
|
||||||
|
|
||||||
// Holds the socket factory that is used to generate the socket in the connection
|
// Holds the socket factory that is used to generate the socket in the connection
|
||||||
private SocketFactory socketFactory;
|
private SocketFactory socketFactory;
|
||||||
|
|
||||||
|
@ -432,27 +429,6 @@ public class ConnectionConfiguration implements Cloneable {
|
||||||
public void setDebuggerEnabled(boolean debuggerEnabled) {
|
public void setDebuggerEnabled(boolean debuggerEnabled) {
|
||||||
this.debuggerEnabled = debuggerEnabled;
|
this.debuggerEnabled = debuggerEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets if the reconnection mechanism is allowed to be used. By default
|
|
||||||
* reconnection is allowed.
|
|
||||||
*
|
|
||||||
* @param isAllowed if the reconnection mechanism should be enabled for this connection.
|
|
||||||
*/
|
|
||||||
public void setReconnectionAllowed(boolean isAllowed) {
|
|
||||||
this.reconnectionAllowed = isAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if the reconnection mechanism is allowed to be used. By default reconnection is
|
|
||||||
* allowed. You can disable the reconnection mechanism with {@link
|
|
||||||
* #setReconnectionAllowed(boolean)}.
|
|
||||||
*
|
|
||||||
* @return true, if the reconnection mechanism is enabled.
|
|
||||||
*/
|
|
||||||
public boolean isReconnectionAllowed() {
|
|
||||||
return this.reconnectionAllowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the socket factory used to create new xmppConnection sockets.
|
* Sets the socket factory used to create new xmppConnection sockets.
|
||||||
|
|
|
@ -19,7 +19,11 @@ package org.jivesoftware.smack;
|
||||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
/**
|
/**
|
||||||
* Handles the automatic reconnection process. Every time a connection is dropped without
|
* Handles the automatic reconnection process. Every time a connection is dropped without
|
||||||
|
@ -35,180 +39,252 @@ import java.util.logging.Logger;
|
||||||
*
|
*
|
||||||
* @author Francisco Vives
|
* @author Francisco Vives
|
||||||
*/
|
*/
|
||||||
public class ReconnectionManager extends AbstractConnectionListener {
|
public class ReconnectionManager {
|
||||||
private static final Logger LOGGER = Logger.getLogger(ReconnectionManager.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ReconnectionManager.class.getName());
|
||||||
|
|
||||||
// Holds the connection to the server
|
private static final Map<AbstractXMPPConnection, ReconnectionManager> INSTANCES = new WeakHashMap<AbstractXMPPConnection, ReconnectionManager>();
|
||||||
private final AbstractXMPPConnection connection;
|
|
||||||
private Thread reconnectionThread;
|
/**
|
||||||
private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds
|
* Get a instance of ReconnectionManager for the given connection.
|
||||||
|
*
|
||||||
// Holds the state of the reconnection
|
* @param connection
|
||||||
boolean done = false;
|
* @return a ReconnectionManager for the connection.
|
||||||
|
*/
|
||||||
|
public static synchronized ReconnectionManager getInstanceFor(AbstractXMPPConnection connection) {
|
||||||
|
ReconnectionManager reconnectionManager = INSTANCES.get(connection);
|
||||||
|
if (reconnectionManager == null) {
|
||||||
|
reconnectionManager = new ReconnectionManager(connection);
|
||||||
|
INSTANCES.put(connection, reconnectionManager);
|
||||||
|
}
|
||||||
|
return reconnectionManager;
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Create a new PrivacyListManager on every established connection. In the init()
|
|
||||||
// method of PrivacyListManager, we'll add a listener that will delete the
|
|
||||||
// instance when the connection is closed.
|
|
||||||
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||||
public void connectionCreated(XMPPConnection connection) {
|
public void connectionCreated(XMPPConnection connection) {
|
||||||
if (connection instanceof AbstractXMPPConnection) {
|
if (connection instanceof AbstractXMPPConnection) {
|
||||||
connection.addConnectionListener(new ReconnectionManager((AbstractXMPPConnection) connection));
|
ReconnectionManager.getInstanceFor((AbstractXMPPConnection) connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReconnectionManager(AbstractXMPPConnection connection) {
|
private static boolean enabledPerDefault = false;
|
||||||
this.connection = connection;
|
|
||||||
|
/**
|
||||||
|
* Set if the automatic reconnection mechanism will be enabled per default for new XMPP connections. The default is
|
||||||
|
* 'false'.
|
||||||
|
*
|
||||||
|
* @param enabled
|
||||||
|
*/
|
||||||
|
public static void setEnabledPerDefault(boolean enabled) {
|
||||||
|
enabledPerDefault = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current default reconnection mechanism setting for new XMPP connections.
|
||||||
|
*
|
||||||
|
* @return true if new connection will come with an enabled reconnection mechanism
|
||||||
|
*/
|
||||||
|
public static boolean getEnabledPerDefault() {
|
||||||
|
return enabledPerDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holds the connection to the server
|
||||||
|
private final WeakReference<AbstractXMPPConnection> weakRefConnection;
|
||||||
|
private final int randomBase = new Random().nextInt(13) + 2; // between 2 and 15 seconds
|
||||||
|
private final Runnable reconnectionRunnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag that indicates if a reconnection should be attempted when abruptly disconnected
|
||||||
|
*/
|
||||||
|
private boolean automaticReconnectEnabled = false;
|
||||||
|
|
||||||
|
boolean done = false;
|
||||||
|
|
||||||
|
private Thread reconnectionThread;
|
||||||
|
|
||||||
|
private ReconnectionManager(AbstractXMPPConnection connection) {
|
||||||
|
weakRefConnection = new WeakReference<AbstractXMPPConnection>(connection);
|
||||||
|
|
||||||
|
reconnectionRunnable = new Thread() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the current number of reconnection attempts
|
||||||
|
*/
|
||||||
|
private int attempts = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of seconds until the next reconnection attempt.
|
||||||
|
*
|
||||||
|
* @return the number of seconds until the next reconnection attempt.
|
||||||
|
*/
|
||||||
|
private int timeDelay() {
|
||||||
|
attempts++;
|
||||||
|
if (attempts > 13) {
|
||||||
|
return randomBase * 6 * 5; // between 2.5 and 7.5 minutes (~5 minutes)
|
||||||
|
}
|
||||||
|
if (attempts > 7) {
|
||||||
|
return randomBase * 6; // between 30 and 90 seconds (~1 minutes)
|
||||||
|
}
|
||||||
|
return randomBase; // 10 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The process will try the reconnection until the connection succeed or the user cancel it
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
final AbstractXMPPConnection connection = weakRefConnection.get();
|
||||||
|
if (connection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The process will try to reconnect until the connection is established or
|
||||||
|
// the user cancel the reconnection process AbstractXMPPConnection.disconnect().
|
||||||
|
while (isReconnectionPossible(connection)) {
|
||||||
|
// Find how much time we should wait until the next reconnection
|
||||||
|
int remainingSeconds = timeDelay();
|
||||||
|
// Sleep until we're ready for the next reconnection attempt. Notify
|
||||||
|
// listeners once per second about how much time remains before the next
|
||||||
|
// reconnection attempt.
|
||||||
|
while (isReconnectionPossible(connection) && remainingSeconds > 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
remainingSeconds--;
|
||||||
|
for (ConnectionListener listener : connection.connectionListeners) {
|
||||||
|
listener.reconnectingIn(remainingSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
// We don't need to handle spurious interrupts, in the worst case, this will cause to
|
||||||
|
// reconnect a few seconds earlier, depending on how many (spurious) interrupts arrive while
|
||||||
|
// sleep() is called.
|
||||||
|
LOGGER.log(Level.FINE, "Supurious interrupt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes a reconnection attempt
|
||||||
|
try {
|
||||||
|
if (isReconnectionPossible(connection)) {
|
||||||
|
connection.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Fires the failed reconnection notification
|
||||||
|
for (ConnectionListener listener : connection.connectionListeners) {
|
||||||
|
listener.reconnectionFailed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the reconnection mechanism is enable per default, enable it for this ReconnectionManager instance
|
||||||
|
if (getEnabledPerDefault()) {
|
||||||
|
enableAutomaticReconnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the automatic reconnection mechanism. Does nothing if already enabled.
|
||||||
|
*/
|
||||||
|
public synchronized void enableAutomaticReconnection() {
|
||||||
|
if (automaticReconnectEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XMPPConnection connection = weakRefConnection.get();
|
||||||
|
if (connection == null) {
|
||||||
|
throw new IllegalStateException("Connection instance no longer available");
|
||||||
|
}
|
||||||
|
connection.addConnectionListener(connectionListener);
|
||||||
|
automaticReconnectEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the automatic reconnection mechanism. Does nothing if already disabled.
|
||||||
|
*/
|
||||||
|
public synchronized void disableAutomaticReconnection() {
|
||||||
|
if (!automaticReconnectEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XMPPConnection connection = weakRefConnection.get();
|
||||||
|
if (connection == null) {
|
||||||
|
throw new IllegalStateException("Connection instance no longer available");
|
||||||
|
}
|
||||||
|
connection.removeConnectionListener(connectionListener);
|
||||||
|
automaticReconnectEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the automatic reconnection mechanism is enabled. You can disable the reconnection mechanism with
|
||||||
|
* {@link #disableAutomaticReconnection} and enable the mechanism with {@link #enableAutomaticReconnection()}.
|
||||||
|
*
|
||||||
|
* @return true, if the reconnection mechanism is enabled.
|
||||||
|
*/
|
||||||
|
public boolean isAutomaticReconnectEnabled() {
|
||||||
|
return automaticReconnectEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the reconnection mechanism is enabled.
|
* Returns true if the reconnection mechanism is enabled.
|
||||||
*
|
*
|
||||||
* @return true if automatic reconnections are allowed.
|
* @return true if automatic reconnection is allowed.
|
||||||
*/
|
*/
|
||||||
private boolean isReconnectionAllowed() {
|
private boolean isReconnectionPossible(XMPPConnection connection) {
|
||||||
return !done && !connection.isConnected()
|
return !done && !connection.isConnected()
|
||||||
&& connection.getConfiguration().isReconnectionAllowed();
|
&& isAutomaticReconnectEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a reconnection mechanism if it was configured to do that.
|
* Starts a reconnection mechanism if it was configured to do that.
|
||||||
* The algorithm is been executed when the first connection error is detected.
|
* The algorithm is been executed when the first connection error is detected.
|
||||||
* <p/>
|
|
||||||
* The reconnection mechanism will try to reconnect periodically in this way:
|
|
||||||
* <ol>
|
|
||||||
* <li>First it will try 6 times every 10 seconds.
|
|
||||||
* <li>Then it will try 10 times every 1 minute.
|
|
||||||
* <li>Finally it will try indefinitely every 5 minutes.
|
|
||||||
* </ol>
|
|
||||||
*/
|
*/
|
||||||
synchronized protected void reconnect() {
|
private synchronized void reconnect() {
|
||||||
if (this.isReconnectionAllowed()) {
|
XMPPConnection connection = this.weakRefConnection.get();
|
||||||
// Since there is no thread running, creates a new one to attempt
|
if (connection == null) {
|
||||||
// the reconnection.
|
LOGGER.fine("Connection is null, will not reconnect");
|
||||||
// avoid to run duplicated reconnectionThread -- fd: 16/09/2010
|
return;
|
||||||
if (reconnectionThread!=null && reconnectionThread.isAlive()) return;
|
|
||||||
|
|
||||||
reconnectionThread = new Thread() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the current number of reconnection attempts
|
|
||||||
*/
|
|
||||||
private int attempts = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of seconds until the next reconnection attempt.
|
|
||||||
*
|
|
||||||
* @return the number of seconds until the next reconnection attempt.
|
|
||||||
*/
|
|
||||||
private int timeDelay() {
|
|
||||||
attempts++;
|
|
||||||
if (attempts > 13) {
|
|
||||||
return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes)
|
|
||||||
}
|
|
||||||
if (attempts > 7) {
|
|
||||||
return randomBase*6; // between 30 and 90 seconds (~1 minutes)
|
|
||||||
}
|
|
||||||
return randomBase; // 10 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The process will try the reconnection until the connection succeed or the user
|
|
||||||
* cancel it
|
|
||||||
*/
|
|
||||||
public void run() {
|
|
||||||
// The process will try to reconnect until the connection is established or
|
|
||||||
// the user cancel the reconnection process {@link XMPPConnection#disconnect()}
|
|
||||||
while (ReconnectionManager.this.isReconnectionAllowed()) {
|
|
||||||
// Find how much time we should wait until the next reconnection
|
|
||||||
int remainingSeconds = timeDelay();
|
|
||||||
// Sleep until we're ready for the next reconnection attempt. Notify
|
|
||||||
// listeners once per second about how much time remains before the next
|
|
||||||
// reconnection attempt.
|
|
||||||
while (ReconnectionManager.this.isReconnectionAllowed() &&
|
|
||||||
remainingSeconds > 0)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
remainingSeconds--;
|
|
||||||
ReconnectionManager.this
|
|
||||||
.notifyAttemptToReconnectIn(remainingSeconds);
|
|
||||||
}
|
|
||||||
catch (InterruptedException e1) {
|
|
||||||
LOGGER.warning("Sleeping thread interrupted");
|
|
||||||
// Notify the reconnection has failed
|
|
||||||
ReconnectionManager.this.notifyReconnectionFailed(e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Makes a reconnection attempt
|
|
||||||
try {
|
|
||||||
if (ReconnectionManager.this.isReconnectionAllowed()) {
|
|
||||||
connection.connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// Fires the failed reconnection notification
|
|
||||||
ReconnectionManager.this.notifyReconnectionFailed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reconnectionThread.setName("Smack Reconnection Manager");
|
|
||||||
reconnectionThread.setDaemon(true);
|
|
||||||
reconnectionThread.start();
|
|
||||||
}
|
}
|
||||||
|
// Since there is no thread running, creates a new one to attempt
|
||||||
|
// the reconnection.
|
||||||
|
// avoid to run duplicated reconnectionThread -- fd: 16/09/2010
|
||||||
|
if (reconnectionThread != null && reconnectionThread.isAlive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
reconnectionThread = new Thread(reconnectionRunnable);
|
||||||
|
reconnectionThread.setName("Smack Reconnection Manager (" + connection.getConnectionCounter() + ')');
|
||||||
|
reconnectionThread.setDaemon(true);
|
||||||
|
reconnectionThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private final ConnectionListener connectionListener = new AbstractConnectionListener() {
|
||||||
* Fires listeners when a reconnection attempt has failed.
|
|
||||||
*
|
@Override
|
||||||
* @param exception the exception that occured.
|
public void connectionClosed() {
|
||||||
*/
|
done = true;
|
||||||
protected void notifyReconnectionFailed(Exception exception) {
|
|
||||||
if (isReconnectionAllowed()) {
|
|
||||||
for (ConnectionListener listener : connection.connectionListeners) {
|
|
||||||
listener.reconnectionFailed(exception);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Fires listeners when The XMPPConnection will retry a reconnection. Expressed in seconds.
|
public void authenticated(XMPPConnection connection) {
|
||||||
*
|
done = false;
|
||||||
* @param seconds the number of seconds that a reconnection will be attempted in.
|
|
||||||
*/
|
|
||||||
protected void notifyAttemptToReconnectIn(int seconds) {
|
|
||||||
if (isReconnectionAllowed()) {
|
|
||||||
for (ConnectionListener listener : connection.connectionListeners) {
|
|
||||||
listener.reconnectingIn(seconds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectionClosed() {
|
public void connectionClosedOnError(Exception e) {
|
||||||
done = true;
|
done = false;
|
||||||
}
|
if (!isAutomaticReconnectEnabled()) {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void connectionClosedOnError(Exception e) {
|
|
||||||
done = false;
|
|
||||||
if (e instanceof StreamErrorException) {
|
|
||||||
StreamErrorException xmppEx = (StreamErrorException) e;
|
|
||||||
StreamError error = xmppEx.getStreamError();
|
|
||||||
String reason = error.getCode();
|
|
||||||
|
|
||||||
if ("conflict".equals(reason)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
if (e instanceof StreamErrorException) {
|
||||||
|
StreamErrorException xmppEx = (StreamErrorException) e;
|
||||||
|
StreamError error = xmppEx.getStreamError();
|
||||||
|
String reason = error.getCode();
|
||||||
|
|
||||||
if (this.isReconnectionAllowed()) {
|
if ("conflict".equals(reason)) {
|
||||||
this.reconnect();
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnect();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue