From a55b54f20b80a7a9338f381faaeeef272da22884 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 18 Mar 2013 19:58:48 +0000 Subject: [PATCH] SMACK-417 If both PacketReader and PacketWriter fail at the same time, connectionClosedonError() is called two times Refactored notifyConnectionError() and notifyReconnection() from PacketReader to XMPPConnection. Made PacketReader.done and PacketWriter.done volatile. Prevent duplicate connectionClosedonError() calls by making the method synchronzied and protected them with an enter guard: if (packetReader.done && packetWriter.done) return; git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/branches/smack_3_3_0@13568 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smack/PacketReader.java | 46 +------------ .../org/jivesoftware/smack/PacketWriter.java | 4 +- .../jivesoftware/smack/XMPPConnection.java | 68 ++++++++++++++++--- .../jivesoftware/smack/ReconnectionTest.java | 6 +- .../jivesoftware/smack/RosterSmackTest.java | 2 +- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 590dfd951..af1c7682f 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -48,7 +48,7 @@ class PacketReader { private XMPPConnection connection; private XmlPullParser parser; - private boolean done; + volatile boolean done; private String connectionID = null; private Semaphore connectionSemaphore; @@ -155,48 +155,6 @@ class PacketReader { connection.collectors.clear(); } - /** - * Sends out a notification that there was an error with the connection - * and closes the connection. - * - * @param e the exception that causes the connection close event. - */ - void notifyConnectionError(Exception e) { - done = true; - // Closes the connection temporary. A reconnection is possible - connection.shutdown(new Presence(Presence.Type.unavailable)); - // Print the stack trace to help catch the problem - e.printStackTrace(); - // Notify connection listeners of the error. - for (ConnectionListener listener : connection.getConnectionListeners()) { - try { - listener.connectionClosedOnError(e); - } - catch (Exception e2) { - // Catch and print any exception so we can recover - // from a faulty listener - e2.printStackTrace(); - } - } - } - - /** - * Sends a notification indicating that the connection was reconnected successfully. - */ - protected void notifyReconnection() { - // Notify connection listeners of the reconnection. - for (ConnectionListener listener : connection.getConnectionListeners()) { - try { - listener.reconnectionSuccessful(); - } - catch (Exception e) { - // Catch and print any exception so we can recover - // from a faulty listener - e.printStackTrace(); - } - } - } - /** * Resets the parser using the latest connection's reader. Reseting the parser is necessary * when the plain connection has been secured or when a new opening stream element is going @@ -332,7 +290,7 @@ class PacketReader { if (!(done || connection.isSocketClosed())) { // Close the connection and notify connection listeners of the // error. - notifyConnectionError(e); + connection.notifyConnectionError(e); } } } diff --git a/source/org/jivesoftware/smack/PacketWriter.java b/source/org/jivesoftware/smack/PacketWriter.java index 155110cfb..f8b5c359f 100644 --- a/source/org/jivesoftware/smack/PacketWriter.java +++ b/source/org/jivesoftware/smack/PacketWriter.java @@ -44,7 +44,7 @@ class PacketWriter { private Writer writer; private XMPPConnection connection; private final BlockingQueue queue; - private boolean done; + volatile boolean done; /** * Timestamp when the last stanza was sent to the server. This information is used @@ -245,7 +245,7 @@ class PacketWriter { // packetReader could be set to null by an concurrent disconnect() call. // Therefore Prevent NPE exceptions by checking packetReader. if (connection.packetReader != null) { - connection.packetReader.notifyConnectionError(ioe); + connection.notifyConnectionError(ioe); } } } diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java index af7b66750..6512aa981 100644 --- a/source/org/jivesoftware/smack/XMPPConnection.java +++ b/source/org/jivesoftware/smack/XMPPConnection.java @@ -473,6 +473,10 @@ public class XMPPConnection extends Connection { return; } + if (!isConnected()) { + return; + } + shutdown(unavailablePresence); if (roster != null) { @@ -483,9 +487,7 @@ public class XMPPConnection extends Connection { wasAuthenticated = false; packetWriter.cleanup(); - packetWriter = null; packetReader.cleanup(); - packetReader = null; } public void sendPacket(Packet packet) { @@ -614,10 +616,8 @@ public class XMPPConnection extends Connection { */ private void initConnection() throws XMPPException { boolean isFirstInitialization = packetReader == null || packetWriter == null; - if (!isFirstInitialization) { - compressionHandler = null; - serverAckdCompression = false; - } + compressionHandler = null; + serverAckdCompression = false; // Set the reader and writer instance variables initReaderAndWriter(); @@ -661,7 +661,7 @@ public class XMPPConnection extends Connection { } } else if (!wasAuthenticated) { - packetReader.notifyReconnection(); + notifyReconnection(); } } @@ -773,7 +773,7 @@ public class XMPPConnection extends Connection { void startTLSReceived(boolean required) { if (required && config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { - packetReader.notifyConnectionError(new IllegalStateException( + notifyConnectionError(new IllegalStateException( "TLS required by server but not allowed by connection configuration")); return; } @@ -787,7 +787,7 @@ public class XMPPConnection extends Connection { writer.flush(); } catch (IOException e) { - packetReader.notifyConnectionError(e); + notifyConnectionError(e); } } @@ -977,7 +977,7 @@ public class XMPPConnection extends Connection { writer.flush(); } catch (IOException e) { - packetReader.notifyConnectionError(e); + notifyConnectionError(e); } } @@ -1041,7 +1041,7 @@ public class XMPPConnection extends Connection { login(config.getUsername(), config.getPassword(), config.getResource()); } - packetReader.notifyReconnection(); + notifyReconnection(); } catch (XMPPException e) { e.printStackTrace(); @@ -1059,4 +1059,50 @@ public class XMPPConnection extends Connection { this.wasAuthenticated = wasAuthenticated; } } + + /** + * Sends out a notification that there was an error with the connection + * and closes the connection. Also prints the stack trace of the given exception + * + * @param e the exception that causes the connection close event. + */ + synchronized void notifyConnectionError(Exception e) { + // Listeners were already notified of the exception, return right here. + if (packetReader.done && packetWriter.done) return; + + packetReader.done = true; + packetWriter.done = true; + // Closes the connection temporary. A reconnection is possible + shutdown(new Presence(Presence.Type.unavailable)); + // Print the stack trace to help catch the problem + e.printStackTrace(); + // Notify connection listeners of the error. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.connectionClosedOnError(e); + } + catch (Exception e2) { + // Catch and print any exception so we can recover + // from a faulty listener + e2.printStackTrace(); + } + } + } + + /** + * Sends a notification indicating that the connection was reconnected successfully. + */ + protected void notifyReconnection() { + // Notify connection listeners of the reconnection. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.reconnectionSuccessful(); + } + catch (Exception e) { + // Catch and print any exception so we can recover + // from a faulty listener + e.printStackTrace(); + } + } + } } diff --git a/test/org/jivesoftware/smack/ReconnectionTest.java b/test/org/jivesoftware/smack/ReconnectionTest.java index f4cbd0c32..35c085813 100644 --- a/test/org/jivesoftware/smack/ReconnectionTest.java +++ b/test/org/jivesoftware/smack/ReconnectionTest.java @@ -44,7 +44,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Simulates an error in the connection - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.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", @@ -79,7 +79,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Simulates an error in the connection - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.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", @@ -103,7 +103,7 @@ public class ReconnectionTest extends SmackTestCase { connection.addConnectionListener(listener); // Produces a connection error - connection.packetReader.notifyConnectionError(new Exception("Simulated Error")); + connection.notifyConnectionError(new Exception("Simulated Error")); assertEquals( "An error occurs but the ConnectionListener.connectionClosedOnError(e) was not notified", true, listener.connectionClosedOnError); diff --git a/test/org/jivesoftware/smack/RosterSmackTest.java b/test/org/jivesoftware/smack/RosterSmackTest.java index 6436f69f4..8bee8124f 100644 --- a/test/org/jivesoftware/smack/RosterSmackTest.java +++ b/test/org/jivesoftware/smack/RosterSmackTest.java @@ -689,7 +689,7 @@ public class RosterSmackTest extends SmackTestCase { Thread.sleep(200); // Break the connection - getConnection(0).packetReader.notifyConnectionError(new Exception("Simulated Error")); + getConnection(0).notifyConnectionError(new Exception("Simulated Error")); Presence presence = roster.getPresence(getBareJID(1)); assertFalse("Unavailable presence not found for offline user", presence.isAvailable());