feat: virtual thread executor support#32689
Conversation
akka-actor/src/main/scala/akka/dispatch/VirtualThreadConfigurator.scala
Outdated
Show resolved
Hide resolved
akka-actor-tests/src/test/scala/akka/dispatch/VirtualThreadDispatcherSpec.scala
Outdated
Show resolved
Hide resolved
akka-actor/src/main/scala/akka/dispatch/VirtualThreadConfigurator.scala
Outdated
Show resolved
Hide resolved
| throw new IllegalStateException("Virtual thread executors only supported on JDK 21 and newer") | ||
| } | ||
| val ofVirtual = ofVirtualMethod.invoke(null) | ||
| // java.lang.ThreadBuilders.VirtualThreadBuilder is package private |
There was a problem hiding this comment.
How would you write this without reflection?
There was a problem hiding this comment.
isn't the public way like this?
ThreadFactory virtualThreadFactory = Thread.ofVirtual().factory();
There was a problem hiding this comment.
Yep, that's what we are calling here, with a potential name inbetween Thread.ofVirtual().name(virtualThreadName).factory()
There was a problem hiding this comment.
To clarify the comment, the reason why we can't just do ofVirtual.getClass.getMethod("name") is that it is of a package private type, so we need to look up the interface it implements that has the name method instead.
There was a problem hiding this comment.
I was thinking we could use Thread.Builder.OfVirtual, which is the public type. I think this should work:
val ofVirtual = ofVirtualMethod.invoke(null)
val nameMethod = classOf[Thread.Builder.OfVirtual].getMethod("name", classOf[String])
nameMethod.invoke(ofVirtual, virtualThreadName)
There was a problem hiding this comment.
Thread.Builder.OfVirtual only exists on JDK 21 and later so we can't do classOf[Thread.Builder.OfVirtual] and have the code still compile on 11 and 17
There was a problem hiding this comment.
sorry, above comment about ThreadBuilders.VirtualThreadBuilder was misleading me. I thought you reached out for that one. You are already using the one that I suggested.
val ofVirtualInterface = dynamicAccess.getClassFor[AnyRef]("java.lang.Thread$Builder$OfVirtual").get
There was a problem hiding this comment.
Ah, I'll drop the comment, it's not needed and worse if it is confusing :)
…t for blocking io dispatcher
akka-actor/src/main/scala/akka/dispatch/AbstractDispatcher.scala
Outdated
Show resolved
Hide resolved
|
For the record, here are some results of ActorBenchmark comparing direct JFP with virtual threads to see the overhead (Linux, 12 core x86 intel i5): I've also run a benchmark looking at gc, sadly did not save the output, but alloc rate up from 0.001 B per op to 0.309 B, not scaled with troughput: 27mb/s for FJP up to 48mb/s with virtual thread per task, this leads to around 10% more time spent on GC. This proves that it is maybe not a good default default dispatcher for actors, however for a blocking io dispatcher that will anyway have a much lower throughput, mostly waiting for some blocking task, it seems like a reasonable tradeoff. |
| throw new IllegalStateException("Virtual thread executors only supported on JDK 21 and newer") | ||
| } | ||
| val ofVirtual = ofVirtualMethod.invoke(null) | ||
| // java.lang.ThreadBuilders.VirtualThreadBuilder is package private |
There was a problem hiding this comment.
I was thinking we could use Thread.Builder.OfVirtual, which is the public type. I think this should work:
val ofVirtual = ofVirtualMethod.invoke(null)
val nameMethod = classOf[Thread.Builder.OfVirtual].getMethod("name", classOf[String])
nameMethod.invoke(ofVirtual, virtualThreadName)
patriknw
left a comment
There was a problem hiding this comment.
LGTM, with possibly changing misleading comment
| throw new IllegalStateException("Virtual thread executors only supported on JDK 21 and newer") | ||
| } | ||
| val ofVirtual = ofVirtualMethod.invoke(null) | ||
| // java.lang.ThreadBuilders.VirtualThreadBuilder is package private |
There was a problem hiding this comment.
sorry, above comment about ThreadBuilders.VirtualThreadBuilder was misleading me. I thought you reached out for that one. You are already using the one that I suggested.
val ofVirtualInterface = dynamicAccess.getClassFor[AnyRef]("java.lang.Thread$Builder$OfVirtual").get
This adds a new executor config which will make a dispatcher run each scheduled task as a separate, new, virtual thread. This is very useful for blocking logic, for example in futures, but less so for running actors on and has considerable overhead for message intense systems.
Somewhat related issue #31613