This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 8b4ea8858bab738be2b1a68807fdcf61d6e5dc3f 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 f619c59642..8012d7989a 100644 --- a/java/org/apache/tomcat/websocket/Constants.java +++ b/java/org/apache/tomcat/websocket/Constants.java @@ -100,6 +100,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 9843a77115..bf41680478 100644 --- a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java +++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java @@ -240,7 +240,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(); @@ -250,22 +250,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 { @@ -274,7 +279,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 be16756bf4..5385cf038c 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 65b12d85c4..98c0ac90e3 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -207,6 +207,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 60231694cb..c159f1dc99 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