This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit a7cb3a359ee4d77f8e65a722de0cea39317ab618 Author: opencode <[email protected]> AuthorDate: Thu Oct 8 15:42:33 2026 +0200 Send the server shutdown command as UTF-8 and time out the stop connect Catalina.stopServer() wrote the configured shutdown string to the shutdown port one character at a time with OutputStream.write(int), which only emits the low eight bits of each char. Any shutdown string containing a character above U+00FF was corrupted in transit, so the comparison in StandardServer.await() could never match and catalina stop could never succeed. The stop command also opened the socket with the no-timeout constructor, so against a shutdown port that silently drops packets it blocked for the full operating system TCP connect timeout. The command is now written as UTF-8 bytes and the server side reads the raw bytes and decodes them as UTF-8 before matching, so any shutdown string works and the two sides agree on the encoding. ASCII commands, the overwhelmingly common case, are unaffected. The client now connects with a ten second timeout and reports the failure instead of blocking indefinitely. --- java/org/apache/catalina/core/StandardServer.java | 14 +++++++++---- java/org/apache/catalina/startup/Catalina.java | 25 +++++++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/java/org/apache/catalina/core/StandardServer.java b/java/org/apache/catalina/core/StandardServer.java index f093524f23..eec1a03501 100644 --- a/java/org/apache/catalina/core/StandardServer.java +++ b/java/org/apache/catalina/core/StandardServer.java @@ -18,6 +18,7 @@ package org.apache.catalina.core; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -25,6 +26,7 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.security.AccessControlException; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -552,7 +554,7 @@ public final class StandardServer extends LifecycleMBeanBase implements Server { // Wait for the next connection Socket socket = null; - StringBuilder command = new StringBuilder(); + ByteArrayOutputStream commandBytes = new ByteArrayOutputStream(); try { InputStream stream; long acceptStartTime = System.nanoTime(); @@ -598,7 +600,7 @@ public final class StandardServer extends LifecycleMBeanBase implements Server { if (ch < 32 || ch == 127) { break; } - command.append((char) ch); + commandBytes.write(ch); expected--; } } finally { @@ -612,13 +614,17 @@ public final class StandardServer extends LifecycleMBeanBase implements Server { } } + // Decode the received bytes as UTF-8, matching the encoding + // used by the stop command + String command = commandBytes.toString(StandardCharsets.UTF_8); + // Match against our command string - boolean match = command.toString().equals(shutdown); + boolean match = command.equals(shutdown); if (match) { log.info(sm.getString("standardServer.shutdownViaPort")); break; } else { - log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString())); + log.warn(sm.getString("standardServer.invalidShutdownCommand", command)); } } } finally { diff --git a/java/org/apache/catalina/startup/Catalina.java b/java/org/apache/catalina/startup/Catalina.java index 3ade41bfcf..d0f2e1d1e8 100644 --- a/java/org/apache/catalina/startup/Catalina.java +++ b/java/org/apache/catalina/startup/Catalina.java @@ -24,7 +24,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.net.ConnectException; +import java.net.InetSocketAddress; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -80,6 +82,12 @@ public class Catalina { */ public static final String SERVER_XML = "conf/server.xml"; + /** + * Timeout, in milliseconds, for connecting to the shutdown port when + * stopping the server. + */ + private static final int SHUTDOWN_CONNECT_TIMEOUT_MS = 10000; + // ----------------------------------------------------- Instance Variables /** @@ -774,12 +782,17 @@ public class Catalina { // Stop the existing server s = getServer(); if (s.getPortWithOffset() > 0) { - try (Socket socket = new Socket(s.getAddress(), s.getPortWithOffset()); - OutputStream stream = socket.getOutputStream()) { - String shutdown = s.getShutdown(); - for (int i = 0; i < shutdown.length(); i++) { - stream.write(shutdown.charAt(i)); - } + // Use a connect timeout so a shutdown port that silently drops + // packets does not block the stop command indefinitely + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(s.getAddress(), s.getPortWithOffset()), + SHUTDOWN_CONNECT_TIMEOUT_MS); + OutputStream stream = socket.getOutputStream(); + // Send the shutdown command as UTF-8 bytes. Writing the + // characters one at a time with OutputStream.write(int) would + // silently truncate any character above U+00FF and the server + // would then never match the configured shutdown string. + stream.write(s.getShutdown().getBytes(StandardCharsets.UTF_8)); stream.flush(); } catch (ConnectException ce) { log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(), --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
