This is an automated email from the ASF dual-hosted git repository.

sigram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new a9d0bc7ca72 SOLR-18423: CrossDC Consumer: off-by-one error in 
ThreadDump handler. (#4889)
a9d0bc7ca72 is described below

commit a9d0bc7ca7213ec01b83d0ff59e7b570a2c5ce31
Author: Andrzej BiaƂecki <[email protected]>
AuthorDate: Wed Sep 9 11:46:02 2026 +0200

    SOLR-18423: CrossDC Consumer: off-by-one error in ThreadDump handler. 
(#4889)
---
 changelog/unreleased/solr-18423.yml                |   8 ++
 .../solr/crossdc/manager/consumer/ThreadDump.java  |   2 +-
 .../crossdc/manager/consumer/ThreadDumpTest.java   | 126 +++++++++++++++++++++
 3 files changed, 135 insertions(+), 1 deletion(-)

diff --git a/changelog/unreleased/solr-18423.yml 
b/changelog/unreleased/solr-18423.yml
new file mode 100644
index 00000000000..c3ef790cb22
--- /dev/null
+++ b/changelog/unreleased/solr-18423.yml
@@ -0,0 +1,8 @@
+title: CrossDC Consumer off-by-one error in the ThreadDump handler.
+  
+type: fixed
+authors:
+  - name: Andrzej Bialecki
+links:
+  - name: SOLR-18423
+    url: https://issues.apache.org/jira/browse/SOLR-18423
diff --git 
a/solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/ThreadDump.java
 
b/solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/ThreadDump.java
index aed28f2c952..080b27e8846 100644
--- 
a/solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/ThreadDump.java
+++ 
b/solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/ThreadDump.java
@@ -117,7 +117,7 @@ public class ThreadDump {
       for (int i = 0; i < elements.length; i++) {
         final StackTraceElement element = elements[i];
         writer.printf(Locale.ROOT, "    at %s%n", element);
-        for (int j = 1; j < monitors.length; j++) {
+        for (int j = 0; j < monitors.length; j++) {
           final MonitorInfo monitor = monitors[j];
           if (monitor.getLockedStackDepth() == i) {
             writer.printf(Locale.ROOT, "      - locked %s%n", monitor);
diff --git 
a/solr/cross-dc-manager/src/test/org/apache/solr/crossdc/manager/consumer/ThreadDumpTest.java
 
b/solr/cross-dc-manager/src/test/org/apache/solr/crossdc/manager/consumer/ThreadDumpTest.java
new file mode 100644
index 00000000000..67182aa3e30
--- /dev/null
+++ 
b/solr/cross-dc-manager/src/test/org/apache/solr/crossdc/manager/consumer/ThreadDumpTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.crossdc.manager.consumer;
+
+import static org.apache.solr.SolrTestCaseJ4.assumeWorkingMockito;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.management.LockInfo;
+import java.lang.management.MonitorInfo;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Verifies that {@link ThreadDump} helper pairs each locked {@link 
MonitorInfo} with the {@link
+ * StackTraceElement} at the matching stack depth, including a monitor locked 
at stack depth 0. This
+ * bug was inherited from the original Dropwizard implementation.
+ */
+public class ThreadDumpTest {
+
+  @BeforeClass
+  public static void beforeClass() {
+    assumeWorkingMockito();
+  }
+
+  @Test
+  public void testAllLockedMonitorsAreReported() {
+    StackTraceElement frame0 = new StackTraceElement("com.example.Inner", 
"run", "Inner.java", 42);
+    StackTraceElement frame1 =
+        new StackTraceElement("com.example.Middle", "call", "Middle.java", 21);
+    StackTraceElement frame2 = new StackTraceElement("com.example.Outer", 
"outer", "Outer.java", 7);
+    StackTraceElement[] stackTrace = {frame0, frame1, frame2};
+
+    // one monitor locked at the innermost frame (depth 0) and one at the 
outermost (depth 2);
+    // depth 1 intentionally has no locked monitor.
+    MonitorInfo monitorAtDepth0 = mock(MonitorInfo.class);
+    when(monitorAtDepth0.getLockedStackDepth()).thenReturn(0);
+    when(monitorAtDepth0.toString()).thenReturn("MONITOR_AT_DEPTH_0");
+
+    MonitorInfo monitorAtDepth2 = mock(MonitorInfo.class);
+    when(monitorAtDepth2.getLockedStackDepth()).thenReturn(2);
+    when(monitorAtDepth2.toString()).thenReturn("MONITOR_AT_DEPTH_2");
+
+    // monitor at depth 0 is deliberately first in the array, so the old loop 
(starting at
+    // index 1) would silently drop it.
+    MonitorInfo[] lockedMonitors = {monitorAtDepth0, monitorAtDepth2};
+
+    ThreadInfo threadInfo = mock(ThreadInfo.class);
+    when(threadInfo.getThreadName()).thenReturn("test-thread");
+    when(threadInfo.getThreadId()).thenReturn(1L);
+    when(threadInfo.getThreadState()).thenReturn(Thread.State.RUNNABLE);
+    when(threadInfo.getLockInfo()).thenReturn(null);
+    when(threadInfo.getLockOwnerName()).thenReturn(null);
+    when(threadInfo.isSuspended()).thenReturn(false);
+    when(threadInfo.isInNative()).thenReturn(false);
+    when(threadInfo.getStackTrace()).thenReturn(stackTrace);
+    when(threadInfo.getLockedMonitors()).thenReturn(lockedMonitors);
+    when(threadInfo.getLockedSynchronizers()).thenReturn(new LockInfo[0]);
+
+    ThreadMXBean threadMXBean = mock(ThreadMXBean.class);
+    when(threadMXBean.dumpAllThreads(true, true)).thenReturn(new ThreadInfo[] 
{threadInfo});
+
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    new ThreadDump(threadMXBean).dump(out);
+
+    List<String> lines = 
out.toString(StandardCharsets.UTF_8).lines().collect(Collectors.toList());
+
+    int frame0Line = indexOfLineContaining(lines, frame0.toString());
+    int frame1Line = indexOfLineContaining(lines, frame1.toString());
+    int frame2Line = indexOfLineContaining(lines, frame2.toString());
+
+    assertTrue("frame0 should be printed", frame0Line >= 0);
+    assertTrue("frame1 should be printed", frame1Line >= 0);
+    assertTrue("frame2 should be printed", frame2Line >= 0);
+
+    // the monitor locked at depth 0 must be reported immediately after 
frame0's "at" line -
+    // this is the case the off-by-one bug broke
+    assertEquals(
+        "monitor locked at stack depth 0 must immediately follow its frame",
+        "      - locked MONITOR_AT_DEPTH_0",
+        lines.get(frame0Line + 1));
+
+    // depth 1 has no locked monitor, so the very next line must be the next 
stack frame.
+    assertEquals(
+        "no monitor line expected between frame1 and frame2",
+        lines.get(frame2Line),
+        lines.get(frame1Line + 1));
+
+    // the monitor locked at depth 2 must immediately follow frame2's "at" 
line.
+    assertEquals(
+        "monitor locked at stack depth 2 must immediately follow its frame",
+        "      - locked MONITOR_AT_DEPTH_2",
+        lines.get(frame2Line + 1));
+  }
+
+  private static int indexOfLineContaining(List<String> lines, String needle) {
+    for (int i = 0; i < lines.size(); i++) {
+      if (lines.get(i).contains(needle)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+}

Reply via email to