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]
