While debugging some race conditions, I came across this thread pool pattern that is used in several places across OpenEMS (especially in the backend):
private final ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(0, Thread.ofVirtual().factory());
(This example was taken from io.openems.common.bridge.http.AsyncBridgeHttpExecutor)
However, this pattern is flawed: It only allows one concurrent execution to happen, as can be shown using this proof of concept:
package io.openems.common.bridge.http;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
private static final ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(0,
Thread.ofVirtual().factory());
public static void main(String[] args) {
pool.setMaximumPoolSize(10);
for (int i = 0; i < 5; i++) {
final var j = i;
pool.execute(() -> {
System.out.println("Task " + j + " sleeping for 5 seconds...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello from task " + j);
});
}
System.out.println("Submitted 5 tasks, waiting for termination...");
try {
pool.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Oracle’s documentation on Virtual Threads explicitly states that virtual threads should never be pooled, and recommends using the following executor instead:
Executors.newVirtualThreadPerTaskExecutor()
I was considering opening a PR to fix this in the HTTP Bridge, but considering that this pattern is used frequently across the code base, I thought it might be best to discuss it here first. I am assuminig that this can be a large performance bottleneck on backend instances with many edges, however I am not an expert in the area of multithreading.