Issue #2788 - Graceful close of HTTP/2 Connection.

Updates after review.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-03-10 01:29:13 +01:00
parent 916b3e56ec
commit 3a6c9b8049
1 changed files with 52 additions and 70 deletions

View File

@ -102,7 +102,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private boolean connectProtocolEnabled; private boolean connectProtocolEnabled;
private long idleTime; private long idleTime;
private GoAwayFrame closeFrame; private GoAwayFrame closeFrame;
private Callback.Completable closeCallback; private Callback.Completable shutdownCallback;
public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId) public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Generator generator, Session.Listener listener, FlowControlStrategy flowControl, int initialStreamId)
{ {
@ -439,27 +439,17 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Received {}", frame); LOG.debug("Received {}", frame);
while (true) if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.REMOTELY_CLOSED))
{ {
CloseState current = closed.get(); // We received a GO_AWAY, so try to write
if (current == CloseState.NOT_CLOSED) // what's in the queue and then disconnect.
{ closeFrame = frame;
if (closed.compareAndSet(current, CloseState.REMOTELY_CLOSED)) notifyClose(this, frame, new DisconnectCallback());
{ return;
// We received a GO_AWAY, so try to write
// what's in the queue and then disconnect.
closeFrame = frame;
notifyClose(this, frame, new DisconnectCallback());
return;
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Ignored {}, already closed", frame);
return;
}
} }
if (LOG.isDebugEnabled())
LOG.debug("Ignored {}, already closed", frame);
} }
@Override @Override
@ -671,60 +661,42 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
@Override @Override
public boolean close(int error, String reason, Callback callback) public boolean close(int error, String reason, Callback callback)
{ {
while (true) if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{ {
CloseState current = closed.get(); if (LOG.isDebugEnabled())
if (current == CloseState.NOT_CLOSED) LOG.debug("Closing {}/{}", error, reason);
{ closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason);
if (closed.compareAndSet(current, CloseState.LOCALLY_CLOSED)) control(null, callback, closeFrame);
{ return true;
if (LOG.isDebugEnabled())
LOG.debug("Closing {}/{}", error, reason);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason);
control(null, callback, closeFrame);
return true;
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring close {}/{}, already closed", error, reason);
callback.succeeded();
return false;
}
} }
if (LOG.isDebugEnabled())
LOG.debug("Ignoring close {}/{}, already closed", error, reason);
callback.succeeded();
return false;
} }
@Override @Override
public CompletableFuture<Void> shutdown() public CompletableFuture<Void> shutdown()
{ {
while (true) if (closed.compareAndSet(CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED))
{ {
CloseState current = closed.get(); if (LOG.isDebugEnabled())
if (current == CloseState.NOT_CLOSED) LOG.debug("Shutting down {}", this);
{ closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, ErrorCode.NO_ERROR.code, "shutdown");
if (closed.compareAndSet(current, CloseState.LOCALLY_CLOSED)) shutdownCallback = new Callback.Completable();
{ // Only send the close frame when we can flip Hi and Lo = 0, see onStreamClosed().
if (LOG.isDebugEnabled()) if (streamCount.compareAndSet(0, 1, 0, 0))
LOG.debug("Shutting down {}", this); control(null, shutdownCallback, closeFrame);
closeFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, ErrorCode.NO_ERROR.code, "shutdown"); return shutdownCallback;
closeCallback = new Callback.Completable();
// Only send the close frame when we can flip Hi and Lo = 0, see onStreamClosed().
if (streamCount.compareAndSet(0, 1, 0, 0))
control(null, closeCallback, closeFrame);
return closeCallback;
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring shutdown, already closed");
Callback.Completable result = closeCallback;
// Result may be null if the shutdown is in progress,
// don't wait and return a completed CompletableFuture.
return result != null ? result : CompletableFuture.completedFuture(null);
}
} }
if (LOG.isDebugEnabled())
LOG.debug("Ignoring shutdown, already closed");
Callback.Completable result = shutdownCallback;
// Result may be null if the shutdown is in progress,
// don't wait and return a completed CompletableFuture.
return result != null ? result : CompletableFuture.completedFuture(null);
} }
private GoAwayFrame newGoAwayFrame(CloseState closeState, int error, String reason) private GoAwayFrame newGoAwayFrame(CloseState closeState, int error, String reason)
@ -1075,13 +1047,23 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void onStreamClosed(IStream stream) protected void onStreamClosed(IStream stream)
{ {
if (streamCount.addAndGetLo(-1) == 0) Callback callback = null;
while (true)
{ {
Callback.Completable callback = closeCallback; long encoded = streamCount.get();
// Only send the close frame if we can flip Hi, see shutdown(). int closed = AtomicBiInteger.getHi(encoded);
if (callback != null && streamCount.compareAndSet(0, 1, 0, 0)) int streams = AtomicBiInteger.getLo(encoded) - 1;
control(null, callback, closeFrame); if (streams == 0 && closed == 0)
{
callback = shutdownCallback;
closed = 1;
}
if (streamCount.compareAndSet(encoded, closed, streams))
break;
} }
// Only send the close frame if we can flip Hi and Lo = 0, see shutdown().
if (callback != null)
control(null, callback, closeFrame);
} }
@Override @Override