Skip to content

Commit a382d4c

Browse files
refactor: Improve thread stack depth testing in ThreadDumpUtilsSuite
- Updated the deepStack function to prevent tail-call optimization, ensuring a deep stack trace for testing. - Enhanced assertions to verify the total stack depth and the correctness of the truncation message in thread dumps. - Streamlined the test logic for better clarity and accuracy in validating stack depth limitations.
1 parent a8569ec commit a382d4c

1 file changed

Lines changed: 36 additions & 21 deletions

File tree

kyuubi-common/src/test/scala/org/apache/kyuubi/util/ThreadDumpUtilsSuite.scala

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,19 @@ class ThreadDumpUtilsSuite extends KyuubiFunSuite {
7070
val stackDepth = 100
7171
val limit = 10
7272

73-
// Recursive function to create a deep stack
74-
def deepStack(n: Int): Unit = {
73+
// This function is INTENTIONALLY NOT tail-recursive.
74+
// By performing an operation after the recursive call, we prevent the compiler
75+
// from optimizing it into a loop, thus forcing a deep stack trace.
76+
def deepStack(n: Int): Int = {
7577
if (n > 0) {
76-
deepStack(n - 1)
78+
val res = deepStack(n - 1)
79+
res + 1 // This operation breaks tail-call optimization
7780
} else {
78-
readyLatch.countDown() // Signal that the thread is ready
79-
try Thread.sleep(5000) // Keep thread alive for dumping
80-
catch { case _: InterruptedException => Thread.currentThread().interrupt() }
81+
readyLatch.countDown() // Signal that the thread has reached the bottom of the stack
82+
try {
83+
Thread.sleep(5000) // Keep thread alive so we can dump it
84+
} catch { case _: InterruptedException => Thread.currentThread().interrupt() }
85+
0 // Base case return
8186
}
8287
}
8388

@@ -86,25 +91,35 @@ class ThreadDumpUtilsSuite extends KyuubiFunSuite {
8691
deepStackThread.setDaemon(true)
8792
deepStackThread.start()
8893

89-
// Wait for the thread to reach its deepest point
9094
assert(readyLatch.await(5, TimeUnit.SECONDS), "Test thread did not initialize in time")
9195

92-
val config = ThreadDumpUtils.DumpConfig(stackDepth = limit)
93-
val dump = ThreadDumpUtils.dumpToString(config)
94-
val section = findThreadSection(dump, "deep-stack-test-thread")
95-
96-
assert(section.isDefined, "Thread 'deep-stack-test-thread' not found in dump")
97-
98-
// Verify that the stack trace is truncated and the message is correct
99-
// Total frames = stackDepth + 1 (for the call to deepStack(0)),
100-
// so we expect (101 - 10) more frames
101-
val expectedMoreFrames = stackDepth + 1 - limit
102-
assert(section.get.contains(s"... (${expectedMoreFrames} more stack frames)"))
103-
104-
// Verify the number of 'at' lines matches the limit
105-
val stackTraceLines = section.get.linesIterator.count(_.trim.startsWith("at "))
96+
// 1. Get the full, unlimited dump to determine the actual total stack depth.
97+
val unlimitedConfig = ThreadDumpUtils.DumpConfig(stackDepth = 0)
98+
val unlimitedDump = ThreadDumpUtils.dumpToString(unlimitedConfig)
99+
val unlimitedSection = findThreadSection(unlimitedDump, "deep-stack-test-thread")
100+
assert(
101+
unlimitedSection.isDefined,
102+
"Thread 'deep-stack-test-thread' not found in unlimited dump")
103+
val totalFrames = unlimitedSection.get.linesIterator.count(_.trim.startsWith("at "))
104+
assert(totalFrames > stackDepth, "Full stack depth is not as deep as expected.")
105+
106+
// 2. Get the limited dump and verify its contents against the full one.
107+
val limitedConfig = ThreadDumpUtils.DumpConfig(stackDepth = limit)
108+
val limitedDump = ThreadDumpUtils.dumpToString(limitedConfig)
109+
val limitedSection = findThreadSection(limitedDump, "deep-stack-test-thread")
110+
assert(limitedSection.isDefined, "Thread 'deep-stack-test-thread' not found in limited dump")
111+
112+
// Verify the number of "at" lines matches the configured limit.
113+
val stackTraceLines = limitedSection.get.linesIterator.count(_.trim.startsWith("at "))
106114
assert(stackTraceLines == limit)
107115

116+
// Verify the truncation message is present and mathematically correct.
117+
val expectedMoreFrames = totalFrames - limit
118+
assert(
119+
limitedSection.get.contains(s"... (${expectedMoreFrames} more stack frames)"),
120+
s"Dump did not contain the expected truncation message. " +
121+
s"Expected '... ($expectedMoreFrames more stack frames)'.")
122+
108123
} finally {
109124
if (deepStackThread != null) deepStackThread.interrupt()
110125
}

0 commit comments

Comments
 (0)