This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit e2cb4bd38443941a07b7586d5af0d718c20d640d 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 | 10 +++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java b/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java index 3e80131f3b..dfdf37d21f 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 643aa95933..28bac2a609 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -390,6 +390,16 @@ </fix> </changelog> </subsection> + <subsection name="WebSocket"> + <changelog> + <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"> <changelog> <fix> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
