Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
db67d8e
Debug freezing test on CI.
AlexeyKuznetsov-DD Aug 11, 2025
b4d7fd6
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 12, 2025
025e232
Trying to reproduce.
AlexeyKuznetsov-DD Aug 12, 2025
b62fc03
Trying to reproduce.
AlexeyKuznetsov-DD Aug 12, 2025
d6d4454
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 13, 2025
835f313
Trying to reproduce.
AlexeyKuznetsov-DD Aug 13, 2025
5892ac5
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 13, 2025
7cafe75
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 14, 2025
01b34c3
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 14, 2025
a1fae6a
Updated flaky tests.
AlexeyKuznetsov-DD Aug 14, 2025
375a9ff
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 14, 2025
60deecd
Trying to reproduce.
AlexeyKuznetsov-DD Aug 14, 2025
0a415ca
Trying to reproduce.
AlexeyKuznetsov-DD Aug 14, 2025
4cfbfd7
Merge branch 'alexeyk/apache-http-5-flaky-tests' into alexeyk/debug-c…
AlexeyKuznetsov-DD Aug 14, 2025
7a263b9
Trying to reproduce.
AlexeyKuznetsov-DD Aug 14, 2025
fac9597
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 15, 2025
26d7a12
Trying to reproduce.
AlexeyKuznetsov-DD Aug 15, 2025
9179563
Trying to reproduce.
AlexeyKuznetsov-DD Aug 15, 2025
16cf188
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 18, 2025
435d111
Trying to reproduce.
AlexeyKuznetsov-DD Aug 18, 2025
fdb23a4
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 19, 2025
9232258
Trying to fix with `useStrictTraceWrites == false`
AlexeyKuznetsov-DD Aug 19, 2025
2c0c4ce
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 19, 2025
118a0ef
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 19, 2025
d6ce19f
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 20, 2025
ba2a706
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 21, 2025
6f7706b
Trying to reproduce.
AlexeyKuznetsov-DD Aug 21, 2025
b8f1dd3
Rolled back `useStrictTraceWrites == false`
AlexeyKuznetsov-DD Aug 21, 2025
4429e5d
Refactored dump logic: start thread in advance and sleep.
AlexeyKuznetsov-DD Aug 21, 2025
ce809d7
Trying to reproduce
AlexeyKuznetsov-DD Aug 21, 2025
79204d9
Refactored thread dump logic
AlexeyKuznetsov-DD Aug 22, 2025
d777c3c
Refactored thread dump logic
AlexeyKuznetsov-DD Aug 22, 2025
dd2ea8c
Trying to reproduce
AlexeyKuznetsov-DD Aug 22, 2025
b0de3f5
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 22, 2025
47459e1
Trying to reproduce.
AlexeyKuznetsov-DD Aug 23, 2025
2951394
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 23, 2025
1fc618f
Trying to reproduce.
AlexeyKuznetsov-DD Aug 23, 2025
497c6eb
Trying to reproduce with heap dump.
AlexeyKuznetsov-DD Aug 23, 2025
f1664c0
Skip non-gradle heap dumps
AlexeyKuznetsov-DD Aug 23, 2025
5cc4f48
Trying to reproduce.
AlexeyKuznetsov-DD Aug 23, 2025
bba600d
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 25, 2025
8c83c37
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 25, 2025
6f4b9a0
Merge branch 'master' into alexeyk/debug-ci-freeze
AlexeyKuznetsov-DD Aug 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitlab/collect_reports.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function process_reports () {
cp -r workspace/$project_to_save/build/reports/* $report_path/ 2>/dev/null || true
cp workspace/$project_to_save/build/hs_err_pid*.log $report_path/ 2>/dev/null || true
cp workspace/$project_to_save/build/javacore*.txt $report_path/ 2>/dev/null || true
cp workspace/$project_to_save/build/*.* $report_path/ 2>/dev/null || true
fi
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

ext {
minJavaVersionForTests = JavaVersion.VERSION_21
// Structured concurrency is a preview feature in Java 21. Methods (e.g. ShutdownOnFailure) used in this instrumentation test are no longer available in Java 25, so we set the max version to 24.
Expand Down Expand Up @@ -56,3 +59,75 @@ compileJava.configure {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType(Test).configureEach {
doFirst {
String fullPath = layout.buildDirectory.asFile.get().absolutePath.replace("dd-trace-java/dd-java-agent",
"dd-trace-java/workspace/dd-java-agent")
println "DEBUG full path: ${fullPath}"

def outDir = new File(fullPath)
outDir.mkdirs()
def outFile = new File(outDir, "${System.currentTimeMillis()}-thread-dump.log")

// single-thread scheduler (daemon)
def scheduler = Executors.newSingleThreadScheduledExecutor({ r ->
Thread t = new Thread(r, "dump-scheduler")
t.setDaemon(true)
return t
})

// schedule the dump job 10s later
def future = scheduler.schedule({
try {
println "DEBUG threads dump: ${outFile.absolutePath}"

new ProcessBuilder("jcmd", "0", "Thread.print", "-l")
.redirectErrorStream(true)
.redirectOutput(outFile)
.start().waitFor()

println "DEBUG heap dumps"
def listOutput = "jcmd -l".execute().text.readLines()
listOutput.each { line ->
println "DEBUG PID: ${line}"

if (!line.contains("gradle")) {
return
}

def pid = line.substring(0, line.indexOf(' '))

def dumpFileName = "${fullPath}/${System.currentTimeMillis()}-${pid}.hprof"

println "DEBUG dump file name: ${dumpFileName}"

def cmd = "jcmd ${pid} GC.heap_dump ${dumpFileName}"

println "DEBUG jcmd: ${cmd}"

def res = cmd.execute().waitFor()

println "DEBUG dump res: ${res}"
}
} catch (Throwable t) {
logger.warn("Dumping failed: ${t.message}", t)
} finally {
scheduler.shutdown()
}
}, 62, TimeUnit.SECONDS)

// store handles for cancellation in doLast
ext.dumpFuture = future
ext.dumpScheduler = scheduler
}

doLast {
// cancel if task completed in <10s
try {
ext.dumpFuture?.cancel(false)
} finally {
ext.dumpScheduler?.shutdownNow()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import com.sun.management.HotSpotDiagnosticMXBean
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.Trace

import javax.management.MBeanServer
import java.lang.management.ManagementFactory
import java.lang.management.ThreadInfo
import java.lang.management.ThreadMXBean
import java.util.concurrent.Callable
import java.util.concurrent.StructuredTaskScope

Expand All @@ -9,10 +14,18 @@ import static datadog.trace.agent.test.utils.TraceUtils.runnableUnderTrace
import static java.time.Instant.now

class StructuredConcurrencyTest extends AgentTestRunner {
@Override
boolean useStrictTraceWrites() {
// TODO: Monitor in CI to validate fix effectiveness against freezes.
return false
ThreadDumpLogger threadDumpLogger

def setup() {
// Use the current feature name as the test name
String testName = "${specificationContext?.currentSpec?.name ?: "unknown-spec"} : ${specificationContext?.currentFeature?.name ?: "unknown-test"}"

threadDumpLogger = new ThreadDumpLogger(testName)
threadDumpLogger.start()
}

def cleanup() {
threadDumpLogger.stop()
}

/**
Expand Down Expand Up @@ -170,4 +183,72 @@ class StructuredConcurrencyTest extends AgentTestRunner {
}
}
}

// 🔒 Private helper class for thread dump logging
private static class ThreadDumpLogger {
private final String testName
private Thread task

ThreadDumpLogger(String testName) {
this.testName = testName
}

void start() {
task = new Thread() {
@Override
void run() {
sleep(20000)

File outputDir = new File("build")
String fullPath = outputDir.absolutePath.replace("dd-trace-java/dd-java-agent",
"dd-trace-java/workspace/dd-java-agent")

outputDir = new File(fullPath)
if (!outputDir.exists()) {
println("Folder not found: " + fullPath)
outputDir.mkdirs()
} else println("Folder found: " + fullPath)

// Use the current feature name as the test name
println("Test name: " + testName)

heapDump(outputDir, "test_1")

def reportFile = new File(outputDir, "${System.currentTimeMillis()}-thread-dump.log")

try (def writer = new FileWriter(reportFile)) {
writer.write("=== Test: ${testName} ===\n")
writer.write("=== Thread Dump Triggered at ${new Date()} ===\n")
writer.write(threadDump(false, false))
writer.write("==============================================\n")
}

heapDump(outputDir, "test_2")
}
}
task.start()
}

static void heapDump(File outputDir, String kind) {
def heapDumpFile = new File(outputDir, "${System.currentTimeMillis()}-heap-dump-${kind}.hprof").absolutePath
MBeanServer server = ManagementFactory.getPlatformMBeanServer()
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class)
mxBean.dumpHeap(heapDumpFile, true)
}

private static String threadDump(boolean lockedMonitors, boolean lockedSynchronizers) {
StringBuffer threadDump = new StringBuffer(System.lineSeparator())
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean()
for(ThreadInfo threadInfo : threadMXBean.dumpAllThreads(lockedMonitors, lockedSynchronizers)) {
threadDump.append(threadInfo.toString())
}

return threadDump.toString()
}

void stop() {
task?.interrupt()
}
}
}
74 changes: 74 additions & 0 deletions dd-java-agent/instrumentation/lettuce-4/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

muzzle {
pass {
Expand All @@ -20,3 +22,75 @@ dependencies {

latestDepTestImplementation group: 'biz.paluch.redis', name: 'lettuce', version: '4.+'
}

tasks.withType(Test).configureEach {
doFirst {
String fullPath = layout.buildDirectory.asFile.get().absolutePath.replace("dd-trace-java/dd-java-agent",
"dd-trace-java/workspace/dd-java-agent")
println "DEBUG full path: ${fullPath}"

def outDir = new File(fullPath)
outDir.mkdirs()
def outFile = new File(outDir, "${System.currentTimeMillis()}-thread-dump.log")

// single-thread scheduler (daemon)
def scheduler = Executors.newSingleThreadScheduledExecutor({ r ->
Thread t = new Thread(r, "dump-scheduler")
t.setDaemon(true)
return t
})

// schedule the dump job 10s later
def future = scheduler.schedule({
try {
println "DEBUG threads dump: ${outFile.absolutePath}"

new ProcessBuilder("jcmd", "0", "Thread.print", "-l")
.redirectErrorStream(true)
.redirectOutput(outFile)
.start().waitFor()

println "DEBUG heap dumps"
def listOutput = "jcmd -l".execute().text.readLines()
listOutput.each { line ->
println "DEBUG PID: ${line}"

if (!line.contains("gradle")) {
return
}

def pid = line.substring(0, line.indexOf(' '))

def dumpFileName = "${fullPath}/${System.currentTimeMillis()}-${pid}.hprof"

println "DEBUG dump file name: ${dumpFileName}"

def cmd = "jcmd ${pid} GC.heap_dump ${dumpFileName}"

println "DEBUG jcmd: ${cmd}"

def res = cmd.execute().waitFor()

println "DEBUG dump res: ${res}"
}
} catch (Throwable t) {
logger.warn("Dumping failed: ${t.message}", t)
} finally {
scheduler.shutdown()
}
}, 62, TimeUnit.SECONDS)

// store handles for cancellation in doLast
ext.dumpFuture = future
ext.dumpScheduler = scheduler
}

doLast {
// cancel if task completed in <10s
try {
ext.dumpFuture?.cancel(false)
} finally {
ext.dumpScheduler?.shutdownNow()
}
}
}
Loading
Loading