This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 81a9391c33 Fix BZ 68884 - improve handling of large scale WebSocket 
disconnects
81a9391c33 is described below

commit 81a9391c33a241743c2632f0ded056dfff6d2cdf
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu May 2 15:13:53 2024 +0100

    Fix BZ 68884 - improve handling of large scale WebSocket disconnects
---
 java/org/apache/tomcat/websocket/Constants.java    |  5 +++++
 .../tomcat/websocket/WsRemoteEndpointImplBase.java | 23 +++++++++++++---------
 java/org/apache/tomcat/websocket/WsSession.java    | 23 +++++++++++++++++++++-
 webapps/docs/changelog.xml                         | 12 +++++++++++
 webapps/docs/web-socket-howto.xml                  | 14 +++++++++++--
 5 files changed, 65 insertions(+), 12 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/Constants.java 
b/java/org/apache/tomcat/websocket/Constants.java
index 16f3f8184f..e394578c51 100644
--- a/java/org/apache/tomcat/websocket/Constants.java
+++ b/java/org/apache/tomcat/websocket/Constants.java
@@ -123,6 +123,11 @@ public class Constants {
     // Default is 30 seconds - setting is in milliseconds
     public static final long DEFAULT_SESSION_CLOSE_TIMEOUT = 
TimeUnit.SECONDS.toMillis(30);
 
+    // Configuration for session close timeout
+    public static final String ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT_PROPERTY = 
"org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT";
+    // Default is 50 milliseconds - setting is in milliseconds
+    public static final long DEFAULT_ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT = 50;
+
     // Configuration for read idle timeout on WebSocket session
     public static final String READ_IDLE_TIMEOUT_MS = 
"org.apache.tomcat.websocket.READ_IDLE_TIMEOUT_MS";
 
diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java 
b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
index 7c28b06901..c36b3051a5 100644
--- a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
+++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
@@ -237,7 +237,7 @@ public abstract class WsRemoteEndpointImplBase implements 
RemoteEndpoint {
 
 
     void sendMessageBlock(CharBuffer part, boolean last) throws IOException {
-        long timeoutExpiry = getTimeoutExpiry();
+        long timeout = getBlockingSendTimeout();
         boolean isDone = false;
         while (!isDone) {
             encoderBuffer.clear();
@@ -247,22 +247,27 @@ public abstract class WsRemoteEndpointImplBase implements 
RemoteEndpoint {
             }
             isDone = !cr.isOverflow();
             encoderBuffer.flip();
-            sendMessageBlock(Constants.OPCODE_TEXT, encoderBuffer, last && 
isDone, timeoutExpiry);
+            sendMessageBlock(Constants.OPCODE_TEXT, encoderBuffer, last && 
isDone, timeout);
         }
         stateMachine.complete(last);
     }
 
 
     void sendMessageBlock(byte opCode, ByteBuffer payload, boolean last) 
throws IOException {
-        sendMessageBlock(opCode, payload, last, getTimeoutExpiry());
+        sendMessageBlock(opCode, payload, last, getBlockingSendTimeout());
     }
 
 
-    private long getTimeoutExpiry() {
-        // Get the timeout before we send the message. The message may
-        // trigger a session close and depending on timing the client
-        // session may close before we can read the timeout.
-        long timeout = getBlockingSendTimeout();
+    void sendMessageBlock(byte opCode, ByteBuffer payload, boolean last, long 
timeout) throws IOException {
+        /*
+         *  Get the timeout before we send the message. The message may 
trigger a session close and depending on timing
+         *  the client session may close before we can read the timeout.
+         */
+        sendMessageBlockInternal(opCode, payload, last, 
getTimeoutExpiry(timeout));
+    }
+
+
+    private long getTimeoutExpiry(long timeout) {
         if (timeout < 0) {
             return Long.MAX_VALUE;
         } else {
@@ -271,7 +276,7 @@ public abstract class WsRemoteEndpointImplBase implements 
RemoteEndpoint {
     }
 
 
-    private void sendMessageBlock(byte opCode, ByteBuffer payload, boolean 
last, long timeoutExpiry)
+    private void sendMessageBlockInternal(byte opCode, ByteBuffer payload, 
boolean last, long timeoutExpiry)
             throws IOException {
         wsSession.updateLastActiveWrite();
 
diff --git a/java/org/apache/tomcat/websocket/WsSession.java 
b/java/org/apache/tomcat/websocket/WsSession.java
index 0c1b2d18dd..7b66df3374 100644
--- a/java/org/apache/tomcat/websocket/WsSession.java
+++ b/java/org/apache/tomcat/websocket/WsSession.java
@@ -688,6 +688,22 @@ public class WsSession implements Session {
     }
 
 
+    /*
+     * Returns the session close timeout in milliseconds
+     */
+    private long getAbnormalSessionCloseSendTimeout() {
+        long result = 0;
+        Object obj = 
userProperties.get(Constants.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT_PROPERTY);
+        if (obj instanceof Long) {
+            result = ((Long) obj).longValue();
+        }
+        if (result <= 0) {
+            result = Constants.DEFAULT_ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT;
+        }
+        return result;
+    }
+
+
     protected void checkCloseTimeout() {
         // Skip the check if no session close timeout has been set.
         if (sessionCloseTimeoutExpiry != null) {
@@ -767,7 +783,12 @@ public class WsSession implements Session {
         }
         msg.flip();
         try {
-            wsRemoteEndpoint.sendMessageBlock(Constants.OPCODE_CLOSE, msg, 
true);
+            if (closeCode == CloseCodes.NORMAL_CLOSURE) {
+                wsRemoteEndpoint.sendMessageBlock(Constants.OPCODE_CLOSE, msg, 
true);
+            } else {
+                wsRemoteEndpoint.sendMessageBlock(Constants.OPCODE_CLOSE, msg, 
true,
+                        getAbnormalSessionCloseSendTimeout());
+            }
         } catch (IOException | IllegalStateException e) {
             // Failed to send close message. Close the socket and let the 
caller
             // deal with the Exception
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index ecd5c7e36b..b5d35bd1e3 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -185,6 +185,18 @@
       </add>
     </changelog>
   </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>68884</bug>: Reduce the write timeout when writing WebSocket close
+        messages for abnormal closes. The timeout defaults to 50 milliseconds
+        and may be controlled using the
+        
<code>org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT</code>
+        property in the user properties collection associated with the 
WebSocket
+        session. (markt)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Web applications">
     <changelog>
       <fix>
diff --git a/webapps/docs/web-socket-howto.xml 
b/webapps/docs/web-socket-howto.xml
index 2aaa808df2..6c0697029f 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -64,13 +64,23 @@
    the timeout to use in milliseconds. For an infinite timeout, use
    <code>-1</code>.</p>
 
-<p>The session close timeout defaults to 30000 milliseconds (30 seconds). This
-   may be changed by setting the property
+<p>The time Tomcat waits for a peer to send a WebSocket session close message
+   after Tomcat has sent a close message to the peer defaults to 30000
+   milliseconds (30 seconds). This may be changed by setting the property
    <code>org.apache.tomcat.websocket.SESSION_CLOSE_TIMEOUT</code> in the user
    properties collection attached to the WebSocket session. The value assigned
    to this property should be a <code>Long</code> and represents the timeout to
    use in milliseconds. Values less than or equal to zero will be ignored.</p>
 
+<p>The write timeout Tomcat uses when writing a session close message when the
+   close is abnormal defaults to 50 milliseconds. This may be changed by 
setting
+   the property
+   <code>org.apache.tomcat.websocket.ABNORMAL_SESSION_CLOSE_SEND_TIMEOUT</code>
+   in the user properties collection attached to the WebSocket session. The
+   value assigned to this property should be a <code>Long</code> and represents
+   the timeout to use in milliseconds. Values less than or equal to zero will 
be
+   ignored.</p>
+
 <p>In addition to the <code>Session.setMaxIdleTimeout(long)</code> method which
    is part of the Jakarta WebSocket API, Tomcat provides greater control of the
    timing out the session due to lack of activity. Setting the property


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to