Project Loom VirtualThreads hang

Robert Engels rengels at ix.netcom.com
Wed Dec 28 00:44:14 UTC 2022


Bummer. That is a really significant limitation. 

At a minimum it seems Thread.yield() should put the calling thread at the end of the run queue. 

It seems this boils down to Loom not having a sense of “fair” thread scheduling. But I also am not sure that is the whole story. Even with far less runnable virtual threads than carrier threads the virtual thread seems stuck in runnable but never runs. 

> On Dec 27, 2022, at 6:27 PM, Dr Heinz M. Kabutz <heinz at javaspecialists.eu> wrote:
> 
> 
> Correct. 
> 
>> On Wed, 28 Dec 2022 at 00:54, robert engels <rengels at ix.netcom.com> wrote:
>> Further diagnosis seems to show that virtual threads are not preemptible - and it seems that the fork-join pool is not stealing work, so if one thread spins - all other threads will be blocked.
>> 
>> Does this sound reasonable? If so, this seems like a significant limitation which will cause all sorts of spin/lock-free code to fail under virtual threads.
>> 
>> 
>>> On Dec 27, 2022, at 4:27 PM, robert engels <rengels at ix.netcom.com> wrote:
>>> 
>>> Hi devs,
>>> 
>>> First, 
>>> 
>>> Thanks for this amazing work!!! It literally solves the only remaining problem Java had.
>>> 
>>> Sorry for the long email.
>>> 
>>> I have been very excited to test-drive Project Loom in JDK19. I have extensive experience in highly concurrent systems/HFT/HPC, so I usually :) know what I am doing.
>>> 
>>> For the easiest test, I took a highly threaded (connection based) server based system (Java port of Go’s nats.io message broker), and converted the threads to virtual threads. The project (jnatsd) is available here. The ‘master’ branch runs very well with excellent performance, but I thought switching to virtual threads might be able to improve things over using async IO, channels, etc. (I have a branch for this that works as well, but it is much more complex, and didn’t provide a huge performance benefit)/
>>> 
>>> There are two branches ’simple_virtual_threads’ and ‘virtual_threads’.
>>> 
>>> In the former, it is literally a 2 line change to enable the virtual threads but it doesn’t work. I narrowed it down the issue that LockSupport.unpark(thread) does not work consistently. At some point, the virtual thread is never scheduled again. I enabled the debug options and I see that the the virtual thread is in:
>>> 
>>> yield0:365, Continuation (jdk.internal.vm)
>>> yield:357, Continuation (jdk.internal.vm)
>>> yieldContinuation:370, VirtualThread (java.lang)
>>> park:499, VirtualThread (java.lang)
>>> parkVirtualThread:2606, System$2 (java.lang)
>>> park:54, VirtualThreads (jdk.internal.misc)
>>> park:369, LockSupport (java.util.concurrent.locks)
>>> run:88, Connection$ConnectionWriter (com.robaho.jnatsd)
>>> run:287, VirtualThread (java.lang)
>>> lambda$new$0:174, VirtualThread$VThreadContinuation (java.lang)
>>> run:-1, VirtualThread$VThreadContinuation$$Lambda$50/0x0000000801065670 (java.lang)
>>> enter0:327, Continuation (jdk.internal.vm)
>>> enter:320, Continuation (jdk.internal.vm)
>>> The instance state is:
>>> 
>>> this = {VirtualThread$VThreadContinuation at 1775} 
>>>  target = {VirtualThread$VThreadContinuation$lambda at 1777} 
>>>   arg$1 = {VirtualThread at 1699} 
>>>    scheduler = {ForkJoinPool at 1781} 
>>>    cont = {VirtualThread$VThreadContinuation at 1775} 
>>>    runContinuation = {VirtualThread$lambda at 1782} 
>>>    state = 2
>>>    parkPermit = true
>>>    carrierThread = null
>>>    termination = null
>>>    eetop = 0
>>>    tid = 76
>>>    name = ""
>>>    interrupted = false
>>>    contextClassLoader = {ClassLoaders$AppClassLoader at 1784} 
>>>    inheritedAccessControlContext = {AccessControlContext at 1785} 
>>>    holder = null
>>>    threadLocals = null
>>>    inheritableThreadLocals = null
>>>    extentLocalBindings = null
>>>    interruptLock = {Object at 1786} 
>>>    parkBlocker = null
>>>    nioBlocker = null
>>>    Thread.cont = null
>>>    uncaughtExceptionHandler = null
>>>    threadLocalRandomSeed = 0
>>>    threadLocalRandomProbe = 0
>>>    threadLocalRandomSecondarySeed = 0
>>>    container = {ThreadContainers$RootContainer$CountingRootContainer at 1787} 
>>>    headStackableScopes = null
>>>   arg$2 = {Connection$ConnectionWriter at 1780} 
>>>  scope = {ContinuationScope at 1776} 
>>>  parent = null
>>>  child = null
>>>  tail = {StackChunk at 1778} 
>>>  done = false
>>>  mounted = false
>>>  yieldInfo = null
>>>  preempted = false
>>>  extentLocalCache = null
>>> scope = {ContinuationScope at 1776} 
>>> child = null
>>> 
>>> As you see in the above, the parkPermit is true, but it never runs again.
>>> 
>>> In the latter branch, ‘virtual_threads’, I changed the lock-free RingBuffer class to use simple synchronized primitives - under the assumption that with virtual threads lock/wait/notify should be highly efficient. It worked, but it was nearly 2x slower than the original thread based lock-free implementation. So, I added a ’spin loop’ in the RingBuffer methods. This code is completely optional and can be no-op’d, and I was able to increase performance to above that of the Thread based version.
>>> 
>>> I dug a little deeper, and decided that using Thread.yield() should be even more efficient than LockSupport.parkNanos(1) - problem is that changing that simple line brings back the hangs. I think there is very little semantic difference between LockSupport.parkNanos(1) and Thread.yield() but the latter should avoid any timer scheduling. The RingBuffer code there is fairly trivial.
>>> 
>>> So, before I dig deeper, is this a known issue that Thread.yield() does not work as expected? Is it is known issue that LockSupport.unpark() fails to reschedule threads?
>>> 
>>> Is it possible because the VirtualThreads do not implement the Java memory model properly?
>>> 
>>> Any ideas how to further diagnose?
>>> 
>>> 
>>> 
>>> 
>>> 
>>> 
>>> 
>> 
> -- 
> Dr Heinz M. Kabutz (PhD CompSci)
> Author of "The Java(tm) Specialists' Newsletter"
> Sun/Oracle Java Champion
> JavaOne Rockstar Speaker
> http://www.javaspecialists.eu
> Tel: +30 69 75 595 262
> Skype: kabutz
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20221227/537c54e9/attachment-0001.htm>


More information about the loom-dev mailing list