2727import java .util .Map ;
2828import java .util .Set ;
2929import java .util .concurrent .ConcurrentHashMap ;
30+ import java .util .concurrent .TimeUnit ;
3031import java .util .concurrent .atomic .AtomicLong ;
3132import java .util .concurrent .atomic .AtomicReference ;
3233
@@ -114,6 +115,7 @@ public class WsSession implements Session {
114115 private volatile long lastActiveRead = System .currentTimeMillis ();
115116 private volatile long lastActiveWrite = System .currentTimeMillis ();
116117 private Map <FutureToSendHandler , FutureToSendHandler > futures = new ConcurrentHashMap <>();
118+ private volatile Long sessionCloseTimeoutExpiry ;
117119
118120
119121 /**
@@ -676,7 +678,14 @@ public void doClose(CloseReason closeReasonMessage, CloseReason closeReasonLocal
676678 */
677679 state .set (State .CLOSED );
678680 // ... and close the network connection.
679- wsRemoteEndpoint .close ();
681+ closeConnection ();
682+ } else {
683+ /*
684+ * Set close timeout. If the client fails to send a close message response within the timeout, the session
685+ * and the connection will be closed when the timeout expires.
686+ */
687+ sessionCloseTimeoutExpiry =
688+ Long .valueOf (System .nanoTime () + TimeUnit .MILLISECONDS .toNanos (getSessionCloseTimeout ()));
680689 }
681690
682691 // Fail any uncompleted messages.
@@ -715,7 +724,7 @@ public void onClose(CloseReason closeReason) {
715724 state .set (State .CLOSED );
716725
717726 // Close the network connection.
718- wsRemoteEndpoint . close ();
727+ closeConnection ();
719728 } else if (state .compareAndSet (State .OUTPUT_CLOSING , State .CLOSING )) {
720729 /*
721730 * The local endpoint sent a close message the the same time as the remote endpoint. The local close is
@@ -727,12 +736,55 @@ public void onClose(CloseReason closeReason) {
727736 * The local endpoint sent the first close message. The remote endpoint has now responded with its own close
728737 * message so mark the session as fully closed and close the network connection.
729738 */
730- wsRemoteEndpoint . close ();
739+ closeConnection ();
731740 }
732741 // CLOSING and CLOSED are NO-OPs
733742 }
734743
735744
745+ private void closeConnection () {
746+ /*
747+ * Close the network connection.
748+ */
749+ wsRemoteEndpoint .close ();
750+ /*
751+ * Don't unregister the session until the connection is fully closed since webSocketContainer is responsible for
752+ * tracking the session close timeout.
753+ */
754+ webSocketContainer .unregisterSession (getSessionMapKey (), this );
755+ }
756+
757+
758+ /*
759+ * Returns the session close timeout in milliseconds
760+ */
761+ protected long getSessionCloseTimeout () {
762+ long result = 0 ;
763+ Object obj = userProperties .get (Constants .SESSION_CLOSE_TIMEOUT_PROPERTY );
764+ if (obj instanceof Long ) {
765+ result = ((Long ) obj ).intValue ();
766+ }
767+ if (result <= 0 ) {
768+ result = Constants .DEFAULT_SESSION_CLOSE_TIMEOUT ;
769+ }
770+ return result ;
771+ }
772+
773+
774+ protected void checkCloseTimeout () {
775+ // Skip the check if no session close timeout has been set.
776+ if (sessionCloseTimeoutExpiry != null ) {
777+ // Check if the timeout has expired.
778+ if (System .nanoTime () - sessionCloseTimeoutExpiry .longValue () > 0 ) {
779+ // Check if the session has been closed in another thread while the timeout was being processed.
780+ if (state .compareAndSet (State .OUTPUT_CLOSED , State .CLOSED )) {
781+ closeConnection ();
782+ }
783+ }
784+ }
785+ }
786+
787+
736788 private void fireEndpointOnClose (CloseReason closeReason ) {
737789
738790 // Fire the onClose event
@@ -805,16 +857,14 @@ private void sendCloseMessage(CloseReason closeReason) {
805857 if (log .isDebugEnabled ()) {
806858 log .debug (sm .getString ("wsSession.sendCloseFail" , id ), e );
807859 }
808- wsRemoteEndpoint . close ();
860+ closeConnection ();
809861 // Failure to send a close message is not unexpected in the case of
810862 // an abnormal closure (usually triggered by a failure to read/write
811863 // from/to the client. In this case do not trigger the endpoint's
812864 // error handling
813865 if (closeCode != CloseCodes .CLOSED_ABNORMALLY ) {
814866 localEndpoint .onError (this , e );
815867 }
816- } finally {
817- webSocketContainer .unregisterSession (getSessionMapKey (), this );
818868 }
819869 }
820870
@@ -947,6 +997,11 @@ public String getQueryString() {
947997 @ Override
948998 public Principal getUserPrincipal () {
949999 checkState ();
1000+ return getUserPrincipalInternal ();
1001+ }
1002+
1003+
1004+ public Principal getUserPrincipalInternal () {
9501005 return userPrincipal ;
9511006 }
9521007
0 commit comments