Commit 0d1a5c45 authored by Tom Evans's avatar Tom Evans

OF-881: Simplify connection close semantics

Avoid recursion and synchronization issues
parent 0cf8db1a
...@@ -144,29 +144,9 @@ public interface Connection { ...@@ -144,29 +144,9 @@ public interface Connection {
* <li>Call notifyEvent all listeners that the channel is shutting down. * <li>Call notifyEvent all listeners that the channel is shutting down.
* <li>Close the socket. * <li>Close the socket.
* </ul> * </ul>
*
* An invocation of this method is equal to invoking {@link #close(boolean)} with a parameter
* that is false.
*/ */
public void close(); public void close();
/**
* Close this session including associated socket connection. The order of
* events for closing the session is:
* <ul>
* <li>Set closing flag to prevent redundant shutdowns.
* <li>Call notifyEvent all listeners that the channel is shutting down.
* <li>Close the socket.
* </ul>
*
* This method takes into account the connection state of the peer. Specifically,
* when the peer is known to be in a disconnected state, no data will be sent
* (otherwise, this method can trigger the delivery of an end-of-stream signal).
*
* @param peerIsKnownToBeDisconnected should be set to true when the peer is known to no longer be available.
*/
public void close( boolean peerIsKnownToBeDisconnected );
/** /**
* Notification message indicating that the server is being shutdown. Implementors * Notification message indicating that the server is being shutdown. Implementors
* should send a stream error whose condition is system-shutdown before closing * should send a stream error whose condition is system-shutdown before closing
......
...@@ -468,20 +468,16 @@ public class SocketConnection implements Connection { ...@@ -468,20 +468,16 @@ public class SocketConnection implements Connection {
@Override @Override
public void close() { public void close() {
close( false );
}
@Override
public void close( boolean peerIsKnownToBeDisconnected ) {
boolean wasClosed = false;
synchronized (this) { synchronized (this) {
if (!isClosed()) { if (isClosed()) {
try { return;
}
if (session != null) { if (session != null) {
session.setStatus(Session.STATUS_CLOSED); session.setStatus(Session.STATUS_CLOSED);
} }
}
if ( !peerIsKnownToBeDisconnected ) {
boolean allowedToWrite = false; boolean allowedToWrite = false;
try { try {
requestWriting(); requestWriting();
...@@ -494,30 +490,19 @@ public class SocketConnection implements Connection { ...@@ -494,30 +490,19 @@ public class SocketConnection implements Connection {
} }
writer.flush(); writer.flush();
} }
catch (IOException e) { catch (Exception e) {
// Do nothing Log.error("Failed to deliver stream close tag: " + e.getMessage());
} }
finally {
// Register that we finished sending data on the connection // Register that we finished sending data on the connection
writeFinished(); writeFinished();
if (allowedToWrite) { if (allowedToWrite) {
releaseWriting(); releaseWriting();
} }
}
}
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error.close")
+ "\n" + this.toString(), e);
}
closeConnection(); closeConnection();
wasClosed = true;
}
}
if (wasClosed) {
notifyCloseListeners(); notifyCloseListeners();
} }
}
@Override @Override
public void systemShutdown() { public void systemShutdown() {
......
...@@ -194,32 +194,26 @@ public abstract class VirtualConnection implements Connection { ...@@ -194,32 +194,26 @@ public abstract class VirtualConnection implements Connection {
*/ */
@Override @Override
public void close() { public void close() {
close( false );
}
@Override
public void close(boolean peerIsKnownToBeDisconnected) {
boolean wasClosed = false;
synchronized (this) { synchronized (this) {
if (!isClosed()) { if (isClosed()) {
try { return;
}
if (session != null) { if (session != null) {
session.setStatus(Session.STATUS_CLOSED); session.setStatus(Session.STATUS_CLOSED);
} }
closeVirtualConnection();
} }
catch (Exception e) {
try {
closeVirtualConnection();
} catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error.close") Log.error(LocaleUtils.getLocalizedString("admin.error.close")
+ "\n" + this.toString(), e); + "\n" + this.toString(), e);
} }
closed = true; closed = true;
wasClosed = true;
}
}
if (wasClosed) {
notifyCloseListeners(); notifyCloseListeners();
} }
}
@Override @Override
public void registerCloseListener(ConnectionCloseListener listener, Object handbackMessage) { public void registerCloseListener(ConnectionCloseListener listener, Object handbackMessage) {
......
...@@ -228,61 +228,34 @@ public class NIOConnection implements Connection { ...@@ -228,61 +228,34 @@ public class NIOConnection implements Connection {
@Override @Override
public void close() { public void close() {
close( false );
}
@Override
public void close( boolean peerIsKnownToBeDisconnected )
{
boolean notifyClose = false;
synchronized ( this ) { synchronized ( this ) {
try // prevent recursion while closing
{ if ( state == State.CLOSED || state == State.CLOSING) {
if ( state == State.CLOSED )
{
return; return;
} }
// This prevents any action after the first invocation of close() on this connection.
if ( state != State.CLOSING )
{
state = State.CLOSING; state = State.CLOSING;
if ( !peerIsKnownToBeDisconnected )
{
try
{
deliverRawText( flashClient ? "</flash:stream>" : "</stream:stream>" );
}
catch ( Exception e )
{
// Ignore
}
}
} }
// deliverRawText might already have forced the state from Closing to Closed. In that case, there's no need try {
// to invoke the CloseListeners again. deliverRawText( flashClient ? "</flash:stream>" : "</stream:stream>" );
if ( state == State.CLOSING ) } catch ( Exception e ) {
{ Log.error("Failed to deliver stream close tag: " + e.getMessage());
notifyClose = true;
}
} }
finally
{
// Ensure that the state of this connection, its session and the MINA context are eventually closed. // Ensure that the state of this connection, its session and the MINA context are eventually closed.
state = State.CLOSED; if ( session != null ) {
if ( session != null )
{
session.setStatus( Session.STATUS_CLOSED ); session.setStatus( Session.STATUS_CLOSED );
} }
try {
ioSession.close( true ); ioSession.close( true );
} catch (Exception e) {
Log.error("Exception while closing MINA session", e);
} }
}
if (notifyClose) state = State.CLOSED;
{
notifyCloseListeners(); // clean up session, etc. notifyCloseListeners(); // clean up session, etc.
} }
}
@Override @Override
public void systemShutdown() { public void systemShutdown() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment