@@ -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