Virtual threads
A virtual thread that blocks inside a synchronized block pins its carrier: the
platform thread underneath cannot be reused while it waits. On a client whose whole point is
concurrency, that turns a cheap thread back into an expensive one. This suite asks whether
OkHttp does it.
Current results
What the suite asserts
BasicLoomTest runs a Dispatcher backed by
newVirtualThreadPerTaskExecutor(), fires twenty concurrent HTTPS requests at a
MockServer container, and asserts that the JVM printed nothing. The JVM is started with
-Djdk.tracePinnedThreads=short, so any pinning event lands on standard output; the
test captures that stream and requires it to be empty. It is an assertion about a side effect,
which is the only way to observe pinning from outside the JDK.
An observed pinning signature
A recorded OkHttp 5.4.0/JDK 21 result produced this signature. Treat it as an example for interpreting the live result above, not as the current result:
VirtualThread[#51]/runnable@ForkJoinPool-1-worker-3 reason:MONITOR
okhttp3.internal.http2.Http2Writer.flush(Http2Writer.kt:131) <== monitors:1
okhttp3.internal.http2.Http2Connection.newStream(Http2Connection.kt:270) <== monitors:2
Http2Connection.newStream holds a monitor across Http2Writer.flush's
blocking write. That is a true statement about the published artifact, not a broken test — so
the suite runs under the loomTest Gradle task with ignoreFailures, and
the assertion stays exactly as written. On JDK 21 through 23, the status collector classifies
this predicted failure as expected and supplies the reason
in place of the stack trace. On JDK 24 and later the exemption does not apply: a pinning trace
there is an unpredicted finding.
Why the answer depends on the JDK
JEP 491, delivered in JDK 24, lets a virtual thread
release its carrier while blocking inside a synchronized method or block. The same
code that pins on 21 should not pin on 24 and later. Recording that difference — same artifact,
same test, different runtime — is exactly what a testbed running against published versions is
for. The suite is gated with @EnabledForJreRange(min = JRE.JAVA_21), and the
daily run includes both 21 and 25, showing the behavior before and after that JDK change.
References
| Document | What it covers |
|---|---|
| JEP 444 | Virtual Threads, final in JDK 21 — the feature under test. |
| JEP 491 | Synchronize Virtual Threads without Pinning, JDK 24 — what should make this finding go away. |
| Core Libraries: Virtual Threads | Pinning, jdk.tracePinnedThreads, and how to read its output. |
| RFC 9113 | HTTP/2 — the multiplexed write path where the monitor is held. |
Reading a result
A green loomTest is the interesting outcome, not the boring one: it means either
the runtime changed underneath the test or OkHttp stopped pinning. Both are worth knowing about
the moment they happen, which is why the finding is recorded on every run rather than muted.
Move a suite into this reporting category only when it is asserting about OkHttp's behaviour —
never to quiet a test that is genuinely broken here. Everything under the test
task stays fatal.