mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-12-27 14:47:59 +01:00
Initial version. SMACK-172
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@5365 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
85781a7158
commit
96e134e21a
2 changed files with 422 additions and 0 deletions
234
source/org/jivesoftware/smack/ReconnectionManager.java
Normal file
234
source/org/jivesoftware/smack/ReconnectionManager.java
Normal file
|
@ -0,0 +1,234 @@
|
|||
package org.jivesoftware.smack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles the reconnection process. Every time a connection is broken, it automatically
|
||||
* tries to reconnect it. The reconnection 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>
|
||||
*
|
||||
* @author Francisco Vives
|
||||
*/
|
||||
public class ReconnectionManager implements ConnectionListener {
|
||||
|
||||
// Holds the time elapsed between each reconnection attempt
|
||||
private int secondBetweenReconnection = 5 * 60; // 5 minutes
|
||||
|
||||
// Holds the thread that produces a periodical reconnection.
|
||||
private Thread reconnectionThread;
|
||||
|
||||
// Holds the connection to the server
|
||||
private XMPPConnection connection;
|
||||
|
||||
// Holds the state of the reconnection
|
||||
boolean done = false;
|
||||
|
||||
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.
|
||||
XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
|
||||
public void connectionEstablished(XMPPConnection connection) {
|
||||
connection.addConnectionListener(new ReconnectionManager(connection));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ReconnectionManager(XMPPConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the reconnection mechanism is allowed to use.
|
||||
*/
|
||||
private boolean isReconnectionAllowed() {
|
||||
return !done && !connection.isConnected()
|
||||
&& connection.getConfiguration().isReconnectionAllowed()
|
||||
&& connection.packetReader != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed between each reconnection attempt.
|
||||
* By default it will try to reconnect every 5 minutes.
|
||||
* It is used when the client has lost the server connection and the XMPPConnection
|
||||
* automatically tries to reconnect.
|
||||
*
|
||||
* @return Returns the number of seconds between reconnection.
|
||||
*/
|
||||
private int getSecondBetweenReconnection() {
|
||||
return secondBetweenReconnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time elapsed between each reconnection attempt.
|
||||
* It is used when the client has lost the server connection and the XMPPConnection
|
||||
* automatically tries to reconnect.
|
||||
*
|
||||
* @param secondBetweenReconnection The number of seconds between reconnection.
|
||||
*/
|
||||
protected void setSecondBetweenReconnection(
|
||||
int secondBetweenReconnection) {
|
||||
this.secondBetweenReconnection = secondBetweenReconnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a reconnection mechanism if it was configured to do that.
|
||||
* 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>
|
||||
*/
|
||||
protected void reconnect() {
|
||||
if (this.isReconnectionAllowed() && reconnectionThread == null) {
|
||||
// Since there is no thread running, creates a new one to attempt
|
||||
// the reconnection.
|
||||
reconnectionThread = new Thread() {
|
||||
/**
|
||||
* Holds the number of reconnection attempts
|
||||
*/
|
||||
private int attempts = 0;
|
||||
private int firstReconnectionPeriod = 7; // 6 attempts
|
||||
private int secondReconnectionPeriod = 10 + firstReconnectionPeriod; // 16 attempts
|
||||
private int firstReconnectionTime = 10; // 10 seconds
|
||||
private int secondReconnectionTime = 1 * 60; // 1 minute
|
||||
private int lastReconnectionTime =
|
||||
getSecondBetweenReconnection(); // user defined in seconds
|
||||
private int remainingSeconds = 0; // The seconds remaining to a reconnection
|
||||
private int notificationPeriod = 1000; // 1 second
|
||||
|
||||
/**
|
||||
* Answer the time it should wait until the next reconnection
|
||||
* attempt
|
||||
*/
|
||||
private int timeDelay() {
|
||||
if (attempts > secondReconnectionPeriod) {
|
||||
return lastReconnectionTime; // 5 minutes
|
||||
}
|
||||
if (attempts > firstReconnectionPeriod) {
|
||||
return secondReconnectionTime; // 1 minute
|
||||
}
|
||||
return firstReconnectionTime; // 10 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* The process will try the reconnection until the connection succeed or the user
|
||||
* cancell 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()) {
|
||||
// Indicate how much time will wait until next reconnection
|
||||
remainingSeconds = timeDelay();
|
||||
// Notifies the remaining time until the next reconnection attempt
|
||||
// every 1 second.
|
||||
while (ReconnectionManager.this.isReconnectionAllowed() &&
|
||||
remainingSeconds > 0) {
|
||||
try {
|
||||
Thread.sleep(notificationPeriod);
|
||||
remainingSeconds = remainingSeconds - 1;
|
||||
ReconnectionManager.this
|
||||
.notifyAttemptToReconnectIn(remainingSeconds);
|
||||
}
|
||||
catch (InterruptedException e1) {
|
||||
e1.printStackTrace();
|
||||
// Notify the reconnection has failed
|
||||
ReconnectionManager.this.notifyReconnectionFailed(e1);
|
||||
}
|
||||
}
|
||||
// Waiting time have finished
|
||||
|
||||
// Makes the reconnection attempt
|
||||
try {
|
||||
if (ReconnectionManager.this.isReconnectionAllowed()) {
|
||||
// Attempts to reconnect.
|
||||
connection.connect();
|
||||
}
|
||||
}
|
||||
catch (XMPPException e) {
|
||||
// Fires the failed reconnection notification
|
||||
ReconnectionManager.this.notifyReconnectionFailed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
reconnectionThread.setName("Smack Reconnection Manager");
|
||||
reconnectionThread.setDaemon(true);
|
||||
reconnectionThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires listeners when a reconnection attempt has failed.
|
||||
*/
|
||||
protected void notifyReconnectionFailed(Exception exception) {
|
||||
List<ConnectionListener> listenersCopy;
|
||||
if (isReconnectionAllowed()) {
|
||||
synchronized (connection.packetReader.connectionListeners) {
|
||||
// Makes a copy since it's possible that a listener will be removed from the list
|
||||
listenersCopy = new ArrayList<ConnectionListener>(
|
||||
connection.packetReader.connectionListeners);
|
||||
for (ConnectionListener listener : listenersCopy) {
|
||||
listener.reconnectionFailed(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires listeners when The XMPPConnection will retry a reconnection. Expressed in seconds.
|
||||
*/
|
||||
protected void notifyAttemptToReconnectIn(int seconds) {
|
||||
List<ConnectionListener> listenersCopy;
|
||||
if (isReconnectionAllowed()) {
|
||||
synchronized (connection.packetReader.connectionListeners) {
|
||||
// Makes a copy since it's possible that a listener will be removed from the list
|
||||
listenersCopy = new ArrayList<ConnectionListener>(
|
||||
connection.packetReader.connectionListeners);
|
||||
for (ConnectionListener listener : listenersCopy) {
|
||||
listener.reconnectingIn(seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void connectionClosed() {
|
||||
done = true;
|
||||
}
|
||||
|
||||
public void connectionClosedOnError(Exception e) {
|
||||
done = false;
|
||||
if (this.isReconnectionAllowed()) {
|
||||
this.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void reconnectingIn(int seconds) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void reconnectionFailed(Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection has successfull gotten connected.
|
||||
*/
|
||||
public void reconectionSuccessful() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
188
test/org/jivesoftware/smack/ReconnectionTest.java
Normal file
188
test/org/jivesoftware/smack/ReconnectionTest.java
Normal file
|
@ -0,0 +1,188 @@
|
|||
package org.jivesoftware.smack;
|
||||
|
||||
import org.jivesoftware.smack.test.SmackTestCase;
|
||||
|
||||
/**
|
||||
* Tests the connection and reconnection mechanism
|
||||
*
|
||||
* @author Francisco Vives
|
||||
*/
|
||||
|
||||
public class ReconnectionTest extends SmackTestCase {
|
||||
|
||||
public ReconnectionTest(String arg0) {
|
||||
super(arg0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an automatic reconnection.
|
||||
* Simulates a connection error and then waits until gets reconnected.
|
||||
*/
|
||||
|
||||
public void testAutomaticReconnection() throws Exception {
|
||||
XMPPConnection connection = getConnection(0);
|
||||
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
||||
connection.addConnectionListener(listener);
|
||||
|
||||
// Simulates an error in the connection
|
||||
connection.packetReader.notifyConnectionError(new Exception("Simulated Error"));
|
||||
Thread.sleep(12000);
|
||||
// After 10 seconds, the reconnection manager must reestablishes the connection
|
||||
assertEquals("The ConnectionListener.connectionStablished() notification was not fired",
|
||||
true, listener.reconnected);
|
||||
assertEquals("The ConnectionListener.reconnectingIn() notification was not fired", 10,
|
||||
listener.attemptsNotifications);
|
||||
assertEquals("The ReconnectionManager algorithm has reconnected without waiting until 0", 0,
|
||||
listener.remainingSeconds);
|
||||
|
||||
// Executes some server interaction testing the connection
|
||||
executeSomeServerInteraction(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a manual reconnection.
|
||||
* Simulates a connection error, disables the reconnection mechanism and then reconnects.
|
||||
*/
|
||||
public void testManualReconnectionWithCancelation() throws Exception {
|
||||
XMPPConnection connection = getConnection(0);
|
||||
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
||||
connection.addConnectionListener(listener);
|
||||
|
||||
// Produces a connection error
|
||||
connection.packetReader.notifyConnectionError(new Exception("Simulated Error"));
|
||||
assertEquals(
|
||||
"An error occurs but the ConnectionListener.connectionClosedOnError(e) was not notified",
|
||||
true, listener.connectionClosedOnError);
|
||||
Thread.sleep(1000);
|
||||
// Cancels the automatic reconnection
|
||||
connection.getConfiguration().setReconnectionAllowed(false);
|
||||
// Waits for a reconnection that must not happened.
|
||||
Thread.sleep(10500);
|
||||
// Cancels the automatic reconnection
|
||||
assertEquals("The connection was stablished but it was not allowed to", false,
|
||||
listener.reconnected);
|
||||
|
||||
// Makes a manual reconnection from an error terminated connection without reconnection
|
||||
connection.connect();
|
||||
|
||||
// Executes some server interaction testing the connection
|
||||
executeSomeServerInteraction(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a manual reconnection after a login.
|
||||
* Closes the connection and then reconnects.
|
||||
*/
|
||||
public void testCloseAndManualReconnection() throws Exception {
|
||||
XMPPConnection connection = getConnection(0);
|
||||
String username = connection.getConfiguration().getUsername();
|
||||
String password = connection.getConfiguration().getPassword();
|
||||
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
||||
connection.addConnectionListener(listener);
|
||||
|
||||
// Produces a normal disconnection
|
||||
connection.disconnect();
|
||||
assertEquals("ConnectionListener.connectionClosed() was not notified",
|
||||
true, listener.connectionClosed);
|
||||
// Waits 10 seconds waiting for a reconnection that must not happened.
|
||||
Thread.sleep(12200);
|
||||
assertEquals("The connection was stablished but it was not allowed to", false,
|
||||
listener.reconnected);
|
||||
|
||||
// Makes a manual reconnection
|
||||
connection.connect();
|
||||
connection.login(username, password);
|
||||
|
||||
// Executes some server interaction testing the connection
|
||||
executeSomeServerInteraction(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a reconnection in a anonymously logged connection.
|
||||
* Closes the connection and then reconnects.
|
||||
*/
|
||||
public void testAnonymousReconnection() throws Exception {
|
||||
XMPPConnection connection = createConnection();
|
||||
connection.connect();
|
||||
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
||||
connection.addConnectionListener(listener);
|
||||
|
||||
// Makes the anounymous login
|
||||
connection.loginAnonymously();
|
||||
|
||||
// Produces a normal disconnection
|
||||
connection.disconnect();
|
||||
assertEquals("ConnectionListener.connectionClosed() was not notified",
|
||||
true, listener.connectionClosed);
|
||||
// Makes a manual reconnection
|
||||
connection.connect();
|
||||
connection.loginAnonymously();
|
||||
assertEquals("Failed the manual connection", true, connection.isAnonymous());
|
||||
}
|
||||
|
||||
private XMPPConnection createConnection() throws Exception {
|
||||
XMPPConnection connection;
|
||||
// Create the configuration
|
||||
ConnectionConfiguration config = new ConnectionConfiguration(getHost(), getPort());
|
||||
config.setTLSEnabled(true);
|
||||
config.setCompressionEnabled(Boolean.getBoolean("test.compressionEnabled"));
|
||||
config.setSASLAuthenticationEnabled(true);
|
||||
connection = new XMPPConnection(config, getSocketFactory());
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute some server interaction in order to test that the regenerated connection works fine.
|
||||
*/
|
||||
private void executeSomeServerInteraction(XMPPConnection connection) throws XMPPException {
|
||||
PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(connection);
|
||||
privacyManager.getPrivacyLists();
|
||||
}
|
||||
|
||||
protected int getMaxConnections() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private class XMPPConnectionTestListener implements ConnectionListener {
|
||||
|
||||
// Variables to support listener notifications verification
|
||||
private boolean connectionClosed = false;
|
||||
private boolean connectionClosedOnError = false;
|
||||
private boolean reconnected = false;
|
||||
private boolean reconnectionFailed = false;
|
||||
private int remainingSeconds = 0;
|
||||
private int attemptsNotifications = 0;
|
||||
private boolean reconnectionCanceled = false;
|
||||
|
||||
/**
|
||||
* Methods to test the listener.
|
||||
*/
|
||||
public void connectionClosed() {
|
||||
connectionClosed = true;
|
||||
}
|
||||
|
||||
public void connectionClosedOnError(Exception e) {
|
||||
connectionClosedOnError = true;
|
||||
}
|
||||
|
||||
public void reconnectionCanceled() {
|
||||
reconnectionCanceled = true;
|
||||
}
|
||||
|
||||
public void reconnectingIn(int seconds) {
|
||||
attemptsNotifications = attemptsNotifications + 1;
|
||||
remainingSeconds = seconds;
|
||||
|
||||
}
|
||||
|
||||
public void reconectionSuccessful() {
|
||||
reconnected = true;
|
||||
}
|
||||
|
||||
public void reconnectionFailed(Exception error) {
|
||||
reconnectionFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue