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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]