This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 4dfe4ca9924cdbb195070c11bb288a5a1cf755cc 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 3dca67b94d..428e2de1ea 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.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; @@ -551,7 +553,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(); @@ -594,7 +596,7 @@ public final class StandardServer extends LifecycleMBeanBase implements Server { if (ch < 32 || ch == 127) { break; } - command.append((char) ch); + commandBytes.write(ch); expected--; } } finally { @@ -608,13 +610,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 1feded95d6..5d514d9d0e 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; @@ -79,6 +81,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 /** @@ -772,12 +780,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]
