Skip to content

Commit 8c1402d

Browse files
jbachorikclaude
andcommitted
Bound in-flight virtual threads in VirtualThreadChurnAntagonist
Unthrottled submission (256 vthreads/~1ms) outpaced the carrier pool's drain rate, so pending VirtualThread/Continuation objects piled up unboundedly and OOM'd in ~45s under a 1g heap. A semaphore now caps in-flight vthreads, throttling submission to actual execution rate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 68ceb93 commit 8c1402d

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

ddprof-stresstest/src/chaos/java/com/datadoghq/profiler/chaos/VirtualThreadChurnAntagonist.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.lang.reflect.Method;
1313
import java.time.Duration;
14+
import java.util.concurrent.Semaphore;
1415
import java.util.concurrent.atomic.AtomicLong;
1516

1617
/**
@@ -19,13 +20,20 @@
1920
*
2021
* <p>Reflectively detects {@code Thread.ofVirtual()} (Java 21+); gracefully
2122
* no-ops on older runtimes.
23+
*
24+
* <p>In-flight virtual threads are bounded by a semaphore: the default
25+
* carrier pool has far fewer platform threads than a batch submits, so an
26+
* unthrottled submission rate outpaces execution and the backlog of pending
27+
* {@code VirtualThread}/{@code Continuation} objects grows without bound
28+
* instead of churning.
2229
*/
2330
public final class VirtualThreadChurnAntagonist implements Antagonist {
2431

2532
private static final Method OF_VIRTUAL = resolveOfVirtual();
2633
private static final Method BUILDER_START = resolveBuilderStart();
2734

2835
private final int batchSize;
36+
private final Semaphore inFlight;
2937

3038
private volatile boolean running;
3139
private Thread driver;
@@ -37,6 +45,7 @@ public VirtualThreadChurnAntagonist() {
3745

3846
public VirtualThreadChurnAntagonist(int batchSize) {
3947
this.batchSize = batchSize;
48+
this.inFlight = new Semaphore(batchSize * 4);
4049
}
4150

4251
@Override
@@ -70,10 +79,26 @@ private void loop() {
7079
while (running) {
7180
for (int i = 0; i < batchSize && running; i++) {
7281
final long seed = System.nanoTime() ^ i;
82+
try {
83+
// Blocks once the backlog is full, throttling submission to the
84+
// carrier pool's actual drain rate instead of hoarding pending
85+
// VirtualThread/Continuation objects.
86+
inFlight.acquire();
87+
} catch (InterruptedException e) {
88+
Thread.currentThread().interrupt();
89+
return;
90+
}
7391
try {
7492
Object builder = OF_VIRTUAL.invoke(null);
75-
BUILDER_START.invoke(builder, (Runnable) () -> sink.addAndGet(burn(seed)));
93+
BUILDER_START.invoke(builder, (Runnable) () -> {
94+
try {
95+
sink.addAndGet(burn(seed));
96+
} finally {
97+
inFlight.release();
98+
}
99+
});
76100
} catch (Throwable t) {
101+
inFlight.release();
77102
return;
78103
}
79104
}

0 commit comments

Comments
 (0)