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

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit d380d3207024fe00e3861b271e57a7b99703eebc
Author: opencode <[email protected]>
AuthorDate: Fri Oct 16 22:51:22 2026 +0200

    Honor the write timeout in AsyncChannelWrapperSecure
    
    The array based write(...) method ignored its timeout and unit
    parameters. WriteTask.run() waited on each socket write with an
    unbounded Future.get() so, if the peer stopped accepting data, the
    write thread blocked forever, the writing flag was held (blocking all
    subsequent writes) and the caller's completion handler never completed.
    
    Compute a deadline from the requested timeout when the write task is
    created, with the AsynchronousSocketChannel semantics that a timeout of
    zero or less means wait indefinitely. Each socket write now waits only
    for the remaining time. On expiry, the pending channel write is
    cancelled and the future is failed with a TimeoutException through the
    existing error handling, which releases the writing flag and notifies
    the completion handler. The single buffer write(ByteBuffer) path, which
    has no timeout parameter, is unchanged.
    
    Found by code review.
---
 .../websocket/AsyncChannelWrapperSecure.java       | 35 ++++++++++++++++++++--
 webapps/docs/changelog.xml                         |  6 ++++
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java 
b/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
index 38a3f8a407..0535b370ad 100644
--- a/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
+++ b/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
@@ -131,7 +131,7 @@ public class AsyncChannelWrapperSecure implements 
AsyncChannelWrapper {
             throw new 
IllegalStateException(sm.getString("asyncChannelWrapperSecure.concurrentWrite"));
         }
 
-        WriteTask writeTask = new WriteTask(srcs, offset, length, future);
+        WriteTask writeTask = new WriteTask(srcs, offset, length, timeout, 
unit, future);
 
         executor.execute(writeTask);
     }
@@ -173,13 +173,24 @@ public class AsyncChannelWrapperSecure implements 
AsyncChannelWrapper {
         private final ByteBuffer[] srcs;
         private final int offset;
         private final int length;
+        // The time by which the write must complete (a System.nanoTime()
+        // based deadline) or -1 if no timeout was requested
+        private final long deadline;
         private final WrapperFuture<Long,?> future;
 
         WriteTask(ByteBuffer[] srcs, int offset, int length, 
WrapperFuture<Long,?> future) {
+            this(srcs, offset, length, 0, null, future);
+        }
+
+        WriteTask(ByteBuffer[] srcs, int offset, int length, long timeout, 
TimeUnit unit,
+                WrapperFuture<Long,?> future) {
             this.srcs = srcs;
             this.future = future;
             this.offset = offset;
             this.length = length;
+            // Match the AsynchronousSocketChannel semantics: a timeout of
+            // zero or less means wait indefinitely
+            this.deadline = unit == null || timeout <= 0 ? -1 : 
System.nanoTime() + unit.toNanos(timeout);
         }
 
         @Override
@@ -221,8 +232,14 @@ public class AsyncChannelWrapperSecure implements 
AsyncChannelWrapper {
                         int toWrite = r.bytesProduced();
                         while (toWrite > 0) {
                             Future<Integer> f = 
socketChannel.write(socketWriteBuffer);
-                            Integer socketWrite = f.get();
-                            toWrite -= socketWrite.intValue();
+                            try {
+                                toWrite -= awaitWrite(f).intValue();
+                            } catch (TimeoutException te) {
+                                // Ensure the pending write is not left
+                                // outstanding on the channel
+                                f.cancel(true);
+                                throw te;
+                            }
                         }
                     }
                 }
@@ -238,6 +255,18 @@ public class AsyncChannelWrapperSecure implements 
AsyncChannelWrapper {
                 future.fail(e);
             }
         }
+
+        private Integer awaitWrite(Future<Integer> f)
+                throws InterruptedException, ExecutionException, 
TimeoutException {
+            if (deadline == -1) {
+                return f.get();
+            }
+            long remaining = deadline - System.nanoTime();
+            if (remaining <= 0) {
+                throw new TimeoutException();
+            }
+            return f.get(remaining, TimeUnit.NANOSECONDS);
+        }
     }
 
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 30a2281d75..6b0914866d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -666,6 +666,12 @@
         <code>Writer</code> and <code>OutputStream</code>. (markt)
       </fix>
       <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
+      <fix>
+        Make the WebSocket client secure channel wrapper,
+        <code>AsyncChannelWrapperSecure</code>, honor the timeout specified for
+        write operations so a stalled peer cannot block the write thread
+        indefinitely. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Web applications">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to