Virtual threads silently never run their body under dd-java-agent with a JDK 26 AOT cache
@mcculls ya está trabajando en esto.
Desde el 17/8/2026.
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Tracer Version(s)
1.63.0
Java Version(s)
Corretto-26.0.1.8.1
JVM Vendor
Amazon Corretto
Bug Report
On JDK 26 with an AOT cache (-XX:AOTMode=on, JEP 483/515), attaching dd-java-agent makes every virtual thread terminate without ever executing its Runnable. Thread.ofVirtual().start(r) returns, the thread goes to TERMINATED, and r is never invoked. No exception is thrown, nothing is logged, and platform threads are unaffected — the work is silently discarded.
Anything that awaits virtual-thread work therefore hangs forever. In our case a Spring Boot service never finished startup (its Kafka producer warmup runs on Executors.newVirtualThreadPerTaskExecutor()), so all pods crashlooped in production while the identical binary without the AOT cache was fine.
The trigger is narrow: the AOT cache must have been recorded by a run that (a) had --enable-final-field-mutation=ALL-UNNAMED (JEP 500) set and (b) actually created a virtual thread. The flag is irrelevant at run time — only what the cache was recorded with matters, which makes this very hard to attribute: the run that breaks does not have to mention the flag at all.
-Ddd.integration.virtual-thread.enabled=false avoids it, which points at datadog.trace.instrumentation.java.lang.jdk21.VirtualThreadInstrumentation.
Reproducer
VirtualThreadAotRepro.java + run.sh (attached). It reports whether the body of a platform thread, a virtual thread, and a task submitted to newVirtualThreadPerTaskExecutor() actually executed:
JAVA_HOME=/path/to/jdk26 DD_AGENT_JAR=/path/to/dd-java-agent.jar ./run.sh
Minimal manual version:
1. record an AOT cache with the JEP 500 flag, from a run that uses a virtual thread
java -XX:AOTMode=record -XX:AOTCacheOutput=app.aot \
--enable-final-field-mutation=ALL-UNNAMED \
-javaagent:dd-java-agent.jar -cp repro.jar VirtualThreadAotRepro
2. run with that cache and the agent — the virtual thread bodies never run
java -XX:AOTMode=on -XX:AOTCache=app.aot \
-javaagent:dd-java-agent.jar -cp repro.jar VirtualThreadAotRepro
Observed vs expected
Expected: three body ran=true lines. Observed in the broken combination:
platform thread : body ran=true alive=false state=TERMINATED
virtual thread : body ran=false alive=false state=TERMINATED <-- Runnable never invoked
vthread executor: body ran=false (latch released=false) <-- task never runs, await times out
Full matrix from run.sh
The agent is attached during recording in every row below, as it is in production (a cache recorded without -javaagent cannot be loaded by a run that adds one — module graph mismatch).
AOT cache recorded with run with result
(no cache) agent + flag PASS
(no cache) no agent, no flag PASS
flag, virtual threads used agent + flag FAIL
flag, virtual threads used agent, no flag FAIL
flag, virtual threads used no agent, flag PASS
flag, virtual threads used no agent, no flag PASS
no flag, virtual threads used agent PASS
flag, virtual threads NOT used agent + flag PASS
So all three of these are required: the JEP 500 flag at record time, a virtual thread created during the recording run, and the agent attached at run time.
Narrowing inside the agent
Run against the broken cache:
additional property result
-Ddd.trace.enabled=false FAIL
-Ddd.trace.runtime.context.field.injection=false FAIL
-Ddd.integration.java_concurrent.enabled=false FAIL
-Ddd.integrations.enabled=false PASS
-Ddd.integration.virtual-thread.enabled=false PASS
Disabling tracing is not enough — instrumentation is what matters, and specifically the virtual-thread integration (java/lang/jdk21/VirtualThreadInstrumentation, whose Construct, Mount, Unmount and AfterDone advice weaves java.lang.VirtualThread). Our reading, which we have not confirmed against the JVM internals, is that when VirtualThread is AOT-linked from a cache recorded under relaxed final-field-mutation semantics, that advice's effect on the thread's task/state field is lost, so the thread has nothing to run. Whatever the precise cause, retransforming an AOT-linked VirtualThread should not be able to silently drop the task.
Expected Behavior
Virtual threads should work :)
Reproduction Code
No response
- Lenguaje dominante
- Java
- Estrellas
- 737
- Forks
- 361
- Merge medio
- 3 d 20 h
- PR fusionados (30 d)
- 173
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de DataDog/dd-trace-java
-
type: feature request
Dificultad 1/5 1-3 horas Aptitud para principiantes 70/100
DataDog/dd-trace-java#10245 · 1 comentario ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 62/100
DataDog/dd-trace-java#12608 ·
-
type: bug report
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
DataDog/dd-trace-java#12597 ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
DataDog/dd-trace-java#12540 · 4 comentarios · 1 asignado ·
-
Dificultad 3/5 1-2 días Aptitud para principiantes 25/100
DataDog/dd-trace-java#12480 ·
Todos los issues de DataDog/dd-trace-java
Issues similares
-
executions.Query — startDate and timeRange filters are sent with inverted comparison operators Abiertoarea/plugin
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
kestra-io/plugin-kestra#190 ·
-
litertlm-android AAR ships no consumer ProGuard rules → "mid == null" SIGABRT in minified apps Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
google-ai-edge/LiteRT-LM#3739 ·
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
-
Add canonical URLs and a sitemap Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
integra-team-red/meet-map#249 ·
-
[Studio][Bug] Cancelled create-user dialog keeps the password and admin switch for the next attempt Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
apache/rocketmq-dashboard#5064 ·