Sample to reproduce:
InputStream in = load();
Jsoup.parse(in, "UTF-8", "http://example.org/")
Running that with -Djdk.tracePinnedThreads=full in Java-21(on a virtual thread) will give the following print:
Thread[#35,ForkJoinPool-1-worker-4,5,CarrierThreads]
…
java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:405) <== monitors:1
org.jsoup.internal.ConstrainableInputStream.read(ConstrainableInputStream.java:64)
…
BufferedInputStream was refactored to be virtual thread friendly, however with a caveat: It becomes unfriendly when inherited (which ConstrainableInputStream does):
public BufferedInputStream(InputStream in, int size) {
…
if (getClass() == BufferedInputStream.class) { // false in case of ConstrainableInputStream
lock = InternalLock.newLockOrNull();
…
} else {
lock = null;
…
}
}
…
public int read(byte[] b, int off, int len) throws IOException {
if (lock != null) {
… // Virtual Thread friendly code
} else {
synchronized (this) {
return implRead(b, off, len); // Line 405
}
}
}
With Loom gaining popularity with the latest LTS release, do you want to reconsider ConstrainableInputStream inheriting from BufferedInputStream? A possible mitigation might be to use composition instead, i.e. inherit from FilterInputStream and use a new instance of BufferedInputStream as source:
class ConstrainableInputStream extends FilterInputStream {
…
static ConstrainableInputStream wrap(InputStream in,…) {
var buffered = new BufferedInputStream(in);
return new ConstrainableInputStream(buffered);
}
}
Sample to reproduce:
Running that with
-Djdk.tracePinnedThreads=fullin Java-21(on a virtual thread) will give the following print:BufferedInputStreamwas refactored to be virtual thread friendly, however with a caveat: It becomes unfriendly when inherited (whichConstrainableInputStreamdoes):With Loom gaining popularity with the latest LTS release, do you want to reconsider
ConstrainableInputStreaminheriting fromBufferedInputStream? A possible mitigation might be to use composition instead, i.e. inherit fromFilterInputStreamand use a new instance ofBufferedInputStreamas source: