2828import java .io .EOFException ;
2929import java .io .IOException ;
3030import java .io .UncheckedIOException ;
31+ import java .lang .invoke .MethodHandles ;
32+ import java .lang .invoke .VarHandle ;
3133import java .net .InetSocketAddress ;
3234import java .net .ProtocolException ;
3335import java .net .URI ;
@@ -287,7 +289,10 @@ public void onMaxHeaderListSizeReached(long size, int maxHeaderListSize) throws
287289 }
288290 }
289291
290- volatile boolean closed ;
292+ private static final int HALF_CLOSED_LOCAL = 1 ;
293+ private static final int HALF_CLOSED_REMOTE = 2 ;
294+ private static final int SHUTDOWN_REQUESTED = 4 ;
295+ volatile int closedState ;
291296
292297 //-------------------------------------
293298 final HttpConnection connection ;
@@ -703,13 +708,15 @@ final int maxConcurrentServerInitiatedStreams() {
703708 }
704709
705710 void close () {
706- Log .logTrace ("Closing HTTP/2 connection: to {0}" , connection .address ());
707- if (connection .channel ().isOpen ()) {
708- GoAwayFrame f = new GoAwayFrame (0 ,
709- ErrorFrame .NO_ERROR ,
710- "Requested by user" .getBytes (UTF_8 ));
711- // TODO: set last stream. For now zero ok.
712- sendFrame (f );
711+ if (markHalfClosedLocal ()) {
712+ if (connection .channel ().isOpen ()) {
713+ Log .logTrace ("Closing HTTP/2 connection: to {0}" , connection .address ());
714+ GoAwayFrame f = new GoAwayFrame (0 ,
715+ ErrorFrame .NO_ERROR ,
716+ "Requested by user" .getBytes (UTF_8 ));
717+ // TODO: set last stream. For now zero ok.
718+ sendFrame (f );
719+ }
713720 }
714721 }
715722
@@ -771,18 +778,17 @@ Throwable getRecordedCause() {
771778 }
772779
773780 void shutdown (Throwable t ) {
774- if (debug .on ()) debug .log (() -> "Shutting down h2c (closed=" +closed +"): " + t );
775- if (closed == true ) return ;
776- synchronized (this ) {
777- if (closed == true ) return ;
778- closed = true ;
779- }
781+ int state = closedState ;
782+ if (debug .on ()) debug .log (() -> "Shutting down h2c (state=" +describeClosedState (state )+"): " + t );
783+ if (!markShutdownRequested ()) return ;
780784 cause .compareAndSet (null , t );
781785 if (Log .errors ()) {
782- if (!( t instanceof EOFException ) || isActive ()) {
786+ if (t != null && (!( t instanceof EOFException ) || isActive () )) {
783787 Log .logError (t );
784788 } else if (t != null ) {
785789 Log .logError ("Shutting down connection: {0}" , t .getMessage ());
790+ } else {
791+ Log .logError ("Shutting down connection" );
786792 }
787793 }
788794 client2 .deleteConnection (this );
@@ -966,7 +972,7 @@ private String checkMaxOrphanedHeadersExceeded(HeaderFrame hf) {
966972 }
967973
968974 final void dropDataFrame (DataFrame df ) {
969- if (closed ) return ;
975+ if (isMarked ( closedState , SHUTDOWN_REQUESTED ) ) return ;
970976 if (debug .on ()) {
971977 debug .log ("Dropping data frame for stream %d (%d payload bytes)" ,
972978 df .streamid (), df .payloadLength ());
@@ -976,7 +982,7 @@ final void dropDataFrame(DataFrame df) {
976982
977983 final void ensureWindowUpdated (DataFrame df ) {
978984 try {
979- if (closed ) return ;
985+ if (isMarked ( closedState , SHUTDOWN_REQUESTED ) ) return ;
980986 int length = df .payloadLength ();
981987 if (length > 0 ) {
982988 windowUpdater .update (length );
@@ -1076,7 +1082,8 @@ private void handleConnectionFrame(Http2Frame frame)
10761082 }
10771083
10781084 boolean isOpen () {
1079- return !closed && connection .channel ().isOpen ();
1085+ return !isMarked (closedState , SHUTDOWN_REQUESTED )
1086+ && connection .channel ().isOpen ();
10801087 }
10811088
10821089 void resetStream (int streamid , int code ) {
@@ -1189,11 +1196,13 @@ private void protocolError(int errorCode, String msg)
11891196 String protocolError = "protocol error" + (msg == null ?"" :(": " + msg ));
11901197 ProtocolException protocolException =
11911198 new ProtocolException (protocolError );
1192- framesDecoder .close (protocolError );
1193- subscriber .stop (protocolException );
1194- if (debug .on ()) debug .log ("Sending GOAWAY due to " + protocolException );
1195- GoAwayFrame frame = new GoAwayFrame (0 , errorCode );
1196- sendFrame (frame );
1199+ if (markHalfClosedLocal ()) {
1200+ framesDecoder .close (protocolError );
1201+ subscriber .stop (protocolException );
1202+ if (debug .on ()) debug .log ("Sending GOAWAY due to " + protocolException );
1203+ GoAwayFrame frame = new GoAwayFrame (0 , errorCode );
1204+ sendFrame (frame );
1205+ }
11971206 shutdown (protocolException );
11981207 }
11991208
@@ -1226,9 +1235,11 @@ private void handlePing(PingFrame frame)
12261235 private void handleGoAway (GoAwayFrame frame )
12271236 throws IOException
12281237 {
1229- shutdown (new IOException (
1230- connection .channel ().getLocalAddress ()
1231- +": GOAWAY received" ));
1238+ if (markHalfClosedLRemote ()) {
1239+ shutdown (new IOException (
1240+ connection .channel ().getLocalAddress ()
1241+ + ": GOAWAY received" ));
1242+ }
12321243 }
12331244
12341245 /**
@@ -1342,7 +1353,7 @@ <T> void putStream(Stream<T> stream, int streamid) {
13421353 // to prevent the SelectorManager thread from exiting until
13431354 // the stream is closed.
13441355 synchronized (this ) {
1345- if (!closed ) {
1356+ if (!isMarked ( closedState , SHUTDOWN_REQUESTED ) ) {
13461357 if (debug .on ()) {
13471358 debug .log ("Opened stream %d" , streamid );
13481359 }
@@ -1353,7 +1364,6 @@ <T> void putStream(Stream<T> stream, int streamid) {
13531364 }
13541365 if (debug .on ()) debug .log ("connection closed: closing stream %d" , stream );
13551366 stream .cancel ();
1356-
13571367 }
13581368
13591369 /**
@@ -1491,7 +1501,7 @@ void sendFrame(Http2Frame frame) {
14911501 }
14921502 publisher .signalEnqueued ();
14931503 } catch (IOException e ) {
1494- if (!closed ) {
1504+ if (!isMarked ( closedState , SHUTDOWN_REQUESTED ) ) {
14951505 Log .logError (e );
14961506 shutdown (e );
14971507 }
@@ -1509,7 +1519,7 @@ void sendDataFrame(DataFrame frame) {
15091519 publisher .enqueue (encodeFrame (frame ));
15101520 publisher .signalEnqueued ();
15111521 } catch (IOException e ) {
1512- if (!closed ) {
1522+ if (!isMarked ( closedState , SHUTDOWN_REQUESTED ) ) {
15131523 Log .logError (e );
15141524 shutdown (e );
15151525 }
@@ -1527,7 +1537,7 @@ void sendUnorderedFrame(Http2Frame frame) {
15271537 publisher .enqueueUnordered (encodeFrame (frame ));
15281538 publisher .signalEnqueued ();
15291539 } catch (IOException e ) {
1530- if (!closed ) {
1540+ if (!isMarked ( closedState , SHUTDOWN_REQUESTED ) ) {
15311541 Log .logError (e );
15321542 shutdown (e );
15331543 }
@@ -1693,4 +1703,60 @@ AbstractAsyncSSLConnection getConnection() {
16931703 return connection ;
16941704 }
16951705 }
1706+
1707+ private boolean isMarked (int state , int mask ) {
1708+ return (state & mask ) == mask ;
1709+ }
1710+
1711+ private boolean markShutdownRequested () {
1712+ return markClosedState (SHUTDOWN_REQUESTED );
1713+ }
1714+
1715+ private boolean markHalfClosedLocal () {
1716+ return markClosedState (HALF_CLOSED_LOCAL );
1717+ }
1718+
1719+ private boolean markHalfClosedLRemote () {
1720+ return markClosedState (HALF_CLOSED_REMOTE );
1721+ }
1722+
1723+ private boolean markClosedState (int flag ) {
1724+ int state , desired ;
1725+ do {
1726+ state = desired = closedState ;
1727+ if ((state & flag ) == flag ) return false ;
1728+ desired = state | flag ;
1729+ } while (!CLOSED_STATE .compareAndSet (this , state , desired ));
1730+ return true ;
1731+ }
1732+
1733+ String describeClosedState (int state ) {
1734+ if (state == 0 ) return "active" ;
1735+ String desc = null ;
1736+ if (isMarked (state , SHUTDOWN_REQUESTED )) {
1737+ desc = "shutdown" ;
1738+ }
1739+ if (isMarked (state , HALF_CLOSED_LOCAL | HALF_CLOSED_REMOTE )) {
1740+ if (desc == null ) return "closed" ;
1741+ else return desc + "+closed" ;
1742+ }
1743+ if (isMarked (state , HALF_CLOSED_LOCAL )) {
1744+ if (desc == null ) return "half-closed-local" ;
1745+ else return desc + "+half-closed-local" ;
1746+ }
1747+ if (isMarked (state , HALF_CLOSED_REMOTE )) {
1748+ if (desc == null ) return "half-closed-remote" ;
1749+ else return desc + "+half-closed-remote" ;
1750+ }
1751+ return "0x" + Integer .toString (state , 16 );
1752+ }
1753+
1754+ private static final VarHandle CLOSED_STATE ;
1755+ static {
1756+ try {
1757+ CLOSED_STATE = MethodHandles .lookup ().findVarHandle (Http2Connection .class , "closedState" , int .class );
1758+ } catch (Exception x ) {
1759+ throw new ExceptionInInitializerError (x );
1760+ }
1761+ }
16961762}
0 commit comments