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 be0c3c6e008903142fdc8c1cae36ce56c1c1fe37 Author: opencode <[email protected]> AuthorDate: Wed Oct 14 10:04:37 2026 +0200 Fix potential NPE in Diagnostics deadlock thread dump findDeadlock() called ThreadMXBean.findDeadlockedThreads() twice, using the first result only for a null check and passing the second, independent snapshot to getThreadInfo(). If the deadlock was resolved between the two calls the second call returned null and getThreadInfo() threw a NullPointerException. Reuse the ids from the first snapshot so there is no time-of-check/time-of-use window between the calls. In addition, ThreadMXBean.getThreadInfo(long[], boolean, boolean) is documented to return null array elements for thread ids that are no longer alive, for example threads that died after the deadlock snapshot. getThreadDump(ThreadInfo[]) passed those elements straight to getThreadDump(ThreadInfo) which dereferences getThreadName(), also throwing a NullPointerException. Skip null entries. Since the deadlock genuinely existed at snapshot time, the deadlock message is still reported; in the extreme case where all threads died the message is followed by an empty dump rather than crashing. findDeadlock() runs on every Manager /threaddump request via Diagnostics.getThreadDump(). The dumpAllThreads() path used by the same method never returns null elements, so the new guard is a no-op there. Verified with TestManagerWebapp which requests /manager/text/threaddump. --- java/org/apache/tomcat/util/Diagnostics.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/java/org/apache/tomcat/util/Diagnostics.java b/java/org/apache/tomcat/util/Diagnostics.java index 05755e80f1..f3383bf746 100644 --- a/java/org/apache/tomcat/util/Diagnostics.java +++ b/java/org/apache/tomcat/util/Diagnostics.java @@ -350,6 +350,11 @@ public class Diagnostics { private static String getThreadDump(ThreadInfo[] tinfos) { StringBuilder sb = new StringBuilder(); for (ThreadInfo tinfo : tinfos) { + // A null entry means the thread died after the thread id + // snapshot was taken. Skip it. + if (tinfo == null) { + continue; + } sb.append(getThreadDump(tinfo)); sb.append(CRLF); } @@ -364,7 +369,15 @@ public class Diagnostics { public static String findDeadlock() { long[] ids = threadMXBean.findDeadlockedThreads(); if (ids != null) { - ThreadInfo[] tinfos = threadMXBean.getThreadInfo(threadMXBean.findDeadlockedThreads(), true, true); + // Use the ids from the first call rather than calling + // findDeadlockedThreads() a second time. A second call may return + // null if the deadlock is resolved in between, in which case + // getThreadInfo() throws a null pointer exception; or it may + // return ids for threads that have since died, in which case + // getThreadInfo() returns null entries. Note getThreadInfo() can + // also return null entries for threads that die after this first + // snapshot; those are skipped by getThreadDump(ThreadInfo[]). + ThreadInfo[] tinfos = threadMXBean.getThreadInfo(ids, true, true); if (tinfos != null) { return sm.getString("diagnostics.deadlockFound") + CRLF + getThreadDump(tinfos); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
