Question on Project Loom (Arthur Navarro)

Arthur Navarro arnavarr at redhat.com
Mon Apr 26 13:31:57 UTC 2021


Thank you for your work on Project Loom and thank you for taking the time
to answer the questions we have on this mailing list. I too am really
excited to see what will come out of it.

I would like to follow up on this question to share some of my thoughts
(and thank you in advance for correcting me where I am wrong).

>     1.  Current implementation of  Java Threads are wrappers around Native
> >     Threads. In linux, threads are Process sharing Virtual Address Space
> >     allocated to the parent process. If a Java thread makes a blocking
> >     system call like Read() on a file descriptor, scheduler adds it to
> the Wait
> >     Queue, to be awakened later. When running in virtual threads.
> blocking it
> >     could be detrimental to the entire JVM. Is my understanding
> correct?. If
> >     it's correct then the only IO allowed from Virtual Thread will be
> >     non-blocking syscalls, any traditional API that uses blocking under
> the
> >     hood will need to be invoked from a non virtual thread.
>

I am sorry if my answer is a little verbose, I can't really express it in a
shorter way. There is a short summary at the end.

Also my answer is based on the ea build of jdk 17-loom of April, I have
very little knowledge of previous versions of the jdk and since I will talk
about some lower-level classes it is worth mentioning that I know close to
nothing about implementations for platforms other than modern linux.

My understanding is that virtual threads love the *semantics* of blocking
I/O since they are synchronous and programmers like synchronous code (for
reasons that are either bound to habits and the current teaching of
programming or to the wiring of our human brains, that I don't think we
know), however virtual threads do not love blocking syscalls at all.

I find the example of the networking socket quite good in this regard :
internally the NioSocketImpl java class is used (a legacy version
PlainSocketImpl exists, I don't know how many systems rely on that one),
and it leverages *non-blocking facilities provided by the OS*. On modern
Linux it uses epoll, on older ones it uses select or poll and on BSD it
uses kqueue (I guess IOCP is used on windows ?).
In Linux when we perform a non-blocking read operation on a networking
socket we don't actually wait for it to complete, we merely register an
event to the epoll instance and ask it to watch the event. The syscall
returns almost immediately and we continue our computation. The thread is
not put in any wait queue.
In Java there is a class (named something like Poller) that communicates
with an epoll instance and maintains a table matching epoll events and java
callbacks.
Now in Loom (finally) when we want to read on a socket we interact with
this class and create an entry containing an epoll event and a java
callback : *the resumption of the virtual thread* (ie. adding the virtual
thread in the run queues consumed by java carrier threads), then we park
the virtual thread (ie. store it in memory and allow its carrier thread to
process the next virtual thread).
When the OS is told that the socket is ready for IO it will signal it to
the epoll instance. The Java Poller will see it and execute the java
callback bound to the epoll event : adding the virtual thread in the run
queues consumed by the carrier threads.
We program *as if *the operation is blocking and synchronous, the Loom
project conceals all the details to make non-blocking asynchronous code
look synchronous while still being non-blocking.NioSocketImpl

Now if we perform a blocking syscall it doesn't matter if we are running a
virtual thread or not. As you said, we switch to kernel mode and the OS
puts the thread (the native linux process on which is mapped the
"traditional" Java thread) in a wait queue. A virtual thread is basically a
java function executed by a java traditional thread so it will not make it
better or worse than blocking a traditional thread.
Typically if we use *PlainSocketImpl* instead of *NioSocketImpl* or any
third-party library using blocking syscalls virtual threads should not be
of any help.
Also, disks I/O on linux are a difficult subject and although io_uring
seems to be a major step towards non-blocking disks I/O with good API
(starting linux 5.1 I believe ?) I don't think that non-blocking tools are
widely used today (linux aio and posix aio for disks don't seem to get much
tractions due to certain limitations), so I wonder if virtual threads would
be of any use for disk IO currently, for Linux at least.

*TL; DR*
If I got it right, the Loom project aims at concealing the asynchronous
nature of the non-blocking IO facilities provided by different OSs, but it
is powerless if you decide to use blocking syscalls.
My understanding is that your question assumed that performing blocking
syscalls with virtual threads was inefficient (which it is if my
understanding is correct), but you wanted to know if it was worse than
doing so with a "traditional" java thread ?
My current understanding of the Loom project tells me that it might add a
negligible overhead due to the creation of the virtual thread but that is
all.


-- 
*Arthur Navarro*

Research and Development associate

He/him/his


More information about the loom-dev mailing list