<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div dir="ltr"></div><div dir="ltr">Bummer. That is a really significant limitation. </div><div dir="ltr"><br></div><div dir="ltr">At a minimum it seems Thread.yield() should put the calling thread at the end of the run queue. </div><div dir="ltr"><br></div><div dir="ltr">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. </div><div dir="ltr"><br><blockquote type="cite">On Dec 27, 2022, at 6:27 PM, Dr Heinz M. Kabutz <heinz@javaspecialists.eu> wrote:<br><br></blockquote></div><blockquote type="cite"><div dir="ltr"><div dir="auto">Correct. </div><div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, 28 Dec 2022 at 00:54, robert engels <<a href="mailto:rengels@ix.netcom.com">rengels@ix.netcom.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word;line-break:after-white-space">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.<div><br></div><div>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.</div></div><div style="word-wrap:break-word;line-break:after-white-space"><div><br><div><br><blockquote type="cite"><div>On Dec 27, 2022, at 4:27 PM, robert engels <<a href="mailto:rengels@ix.netcom.com" target="_blank">rengels@ix.netcom.com</a>> wrote:</div><br><div><div style="word-wrap:break-word;line-break:after-white-space">Hi devs,<div><br></div><div>First, <br><div><br></div><span>Thanks for this amazing work!!! It literally solves the only remaining problem Java had.</span></div><div><span><br></span></div><div><span>Sorry for the long email.</span></div><div><font><span><br></span></font><div>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.</div><div><br></div><div>For the easiest test, I took a highly threaded (connection based) server based system (Java port of Go’s <a href="http://nats.io/" target="_blank">nats.io</a> message broker), and converted the threads to virtual threads. The project (jnatsd) is available <a href="https://github.com/robaho/jnatsd" target="_blank">here</a>. 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)/</div><div><br></div><div>There are two branches ’simple_virtual_threads’ and ‘virtual_threads’.</div><div><br></div><div>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:</div><div><br></div><div><pre>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)
</pre><div>The instance state is:</div></div><div><br></div><div><div>this = {VirtualThread$VThreadContinuation@1775} </div><div> target = {VirtualThread$VThreadContinuation$lambda@1777} </div><div>  arg$1 = {VirtualThread@1699} </div><div>   scheduler = {ForkJoinPool@1781} </div><div>   cont = {VirtualThread$VThreadContinuation@1775} </div><div>   runContinuation = {VirtualThread$lambda@1782} </div><div>   state = 2</div><div>   parkPermit = true</div><div>   carrierThread = null</div><div>   termination = null</div><div>   eetop = 0</div><div>   tid = 76</div><div>   name = ""</div><div>   interrupted = false</div><div>   contextClassLoader = {ClassLoaders$AppClassLoader@1784} </div><div>   inheritedAccessControlContext = {AccessControlContext@1785} </div><div>   holder = null</div><div>   threadLocals = null</div><div>   inheritableThreadLocals = null</div><div>   extentLocalBindings = null</div><div>   interruptLock = {Object@1786} </div><div>   parkBlocker = null</div><div>   nioBlocker = null</div><div>   Thread.cont = null</div><div>   uncaughtExceptionHandler = null</div><div>   threadLocalRandomSeed = 0</div><div>   threadLocalRandomProbe = 0</div><div>   threadLocalRandomSecondarySeed = 0</div><div>   container = {ThreadContainers$RootContainer$CountingRootContainer@1787} </div><div>   headStackableScopes = null</div><div>  arg$2 = {Connection$ConnectionWriter@1780} </div><div> scope = {ContinuationScope@1776} </div><div> parent = null</div><div> child = null</div><div> tail = {StackChunk@1778} </div><div> done = false</div><div> mounted = false</div><div> yieldInfo = null</div><div> preempted = false</div><div> extentLocalCache = null</div><div>scope = {ContinuationScope@1776} </div><div>child = null</div></div><div><br></div><div>As you see in the above, the parkPermit is true, but it never runs again.</div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div>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?</div><div><br></div><div>Is it possible because the VirtualThreads do not implement the Java memory model properly?</div><div><br></div><div>Any ideas how to further diagnose?</div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div><div><br></div></div></div></div></blockquote></div><br></div></div></blockquote></div></div>-- <br><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature">Dr Heinz M. Kabutz (PhD CompSci)<br>Author of "The Java(tm) Specialists' Newsletter"<br>Sun/Oracle Java Champion<br>JavaOne Rockstar Speaker<br><a href="http://www.javaspecialists.eu" target="_blank">http://www.javaspecialists.eu</a><br>Tel: +30 69 75 595 262<br>Skype: kabutz<br></div>
</div></blockquote></body></html>