Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Virtual threads silently never run their body under dd-java-agent with a JDK 26 AOT cache

Abierto
#12,196 4 comentarios 0 reacciones 1 asignado Ver en GitHub

@mcculls ya está trabajando en esto.

Desde el 17/8/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

type: bug report
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

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de DataDog/dd-trace-java

Todos los issues de DataDog/dd-trace-java

Issues similares

Más issues de Java

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.