Skip to content

[feature]: Experimental VirtualThreads support#4793

Merged
WojciechMazur merged 55 commits into
scala-native:mainfrom
WojciechMazur:experimental/virtual-threads
May 12, 2026
Merged

[feature]: Experimental VirtualThreads support#4793
WojciechMazur merged 55 commits into
scala-native:mainfrom
WojciechMazur:experimental/virtual-threads

Conversation

@WojciechMazur
Copy link
Copy Markdown
Contributor

@WojciechMazur WojciechMazur commented Feb 23, 2026

VirtualThreads support work has started in 2024 but was never finished. This branch contains work-in-progress implementation to track current progress. Some of these changes would be extracted

VirtualThreads are going to initially be a Scala 3 only feature due to the lack of Continuations API in Scala 2
This PR focuses only on the scheduling of lightweight threads, does not contains scoped-concurrency primitives.

Currently supports only MacOS (x86_64/aarch64) and Linux (x86_64) - no stable continuations on Windows yet

Known issue: on LLVM 14/ 15 the continuations result in broken state at runtime. Issue not present after upgrade to LLVM 16 (latest version 22 recommended)

@LeeTibbert
Copy link
Copy Markdown
Contributor

Bravo!

Comment thread javalib/src/main/scala-3/java/lang/VirtualThread.scala Outdated
@bishabosha
Copy link
Copy Markdown

bishabosha commented Feb 23, 2026

Unsure of where your roadmap is for Java IO/NIO integration of VirtualThreads but all those API that traditionally block without you even thinking about it (like AbstractPlainSocketImpl#read wrapper oversys.socket.recv) now need before calling to pre-emptively check if they are VT so they can yield and setup a callback to resume the virtual thread when bytes are there (either when the file descriptor is ready to read if nonblocking, or when a background platform thread has done the read), which is i think one reason why giving people control over the inevitable "seamlessly hidden" thread pool that handles these blockers is important.

Edit: or as possibly suggested via JEP proposal - all sockets/files/etc are now in non-blocking mode by default and the "blocking" jdk apis are all rewritten to be in terms of a non blocking event loop that is "hidden" that parks/wakes-up threads at every I/O op

@LeeTibbert
Copy link
Copy Markdown
Contributor

LeeTibbert commented Feb 24, 2026

@bishabosha

 all those API that traditionally block without you even thinking about 
it (like AbstractPlainSocketImpl#read wrapper oversys.socket.recv) now need before
calling to preemptively check if they are VT so they can yield and setup callbacks for 
when the real operation completes, 

Not to ask for too much of your time. Do you know of models or examples of what you describe. Are they likely
to exist in the Scala (3.8.2-ish) repository? Another project?

I've read the Java documentation for there various versions which had/have preview & release versions
of virtual threads. Maybe I will steal some cycles to go back and look for examples which may not
have stood out in prior readings.

I can also always take a look at the 'delimited continuations' code in SN itself. That is two years
old and may not reflect current thinking.

In a similar vein, I've been monitoring various Scala 'async' efforts for a number of years, including SN PR #3978.
I've seen the wanderings but have lost track of the details. Any place I should be looking to come up to speed
on current thinking and efforts? Where do the cool kids hang out these days?

Thank you

For background, I am a SN contributor and one of the people who might be doing enabling SN library work.
My background is in kernel network development, which is highly async.

@bishabosha
Copy link
Copy Markdown

bishabosha commented Feb 25, 2026

Here is the JDK 25 API doc for the getInputStream of a Socket - which hints at a special behavior for a virtual thread reading from the inputstream.

from JEP 425:

Application code in the thread-per-request style can run in a virtual thread for the entire duration of a request, but the virtual thread consumes an OS thread only while it performs calculations on the CPU. The result is the same scalability as the asynchronous style, except it is achieved transparently: When code running in a virtual thread calls a blocking I/O operation in the java.* API, the runtime performs a non-blocking OS call and automatically suspends the virtual thread until it can be resumed later.

im not sure if this is what is actually implemented but that seems to imply all sockets in JDK are now secretly in non-blocking mode, or can you temporarily change the socket to non-blocking while VT checks and then reset it to blocking?

@LeeTibbert
Copy link
Copy Markdown
Contributor

@bishabosha

Thank you for the info. In following the evolution of Virtual Threads from preview thru JDK 25, I had seen
the concerns with blocking/consuming platform threads, especially wrt I/O. I'll have to go back and
study the 21 thru 25 (soon 26) doc again.

@LeeTibbert
Copy link
Copy Markdown
Contributor

Suggestion, since this work is Draft. perhaps it might be wise/prudent to make the first few releases of
SN Virtual Threads opt-in.

Background: Part of the reason that I am a big fan of SN Virtual Threads is that I had a private
sandbox implementation of javalib Stream Gatherers working a couple of years back. The work
never advanced beyond proof-of-concept because the implementation would only have made
sense with Virtual Theads. All things come with time & patience.

@WojciechMazur
Copy link
Copy Markdown
Contributor Author

Virtual threads are always opt in, even on the JDK as these require usage of new Threads API to construct them. Adding additional blocker on the build side would seem redundant.

@LeeTibbert
Copy link
Copy Markdown
Contributor

OK, IIUC Thread#isVirtual' allows library calls to dispatch. JDK >= 21 allows entire program to choose 'Thread.ofVirtual()' or not, as suitable for each thread.

I understand that a good goal is JDK 25 parity with no pinning surprises and synchronized blocks working. If SN has remaining gotchas, the announcement of virtual thread support announcement needs to be carefully worded so that early adopters are not turned off by falling into known, to some, defects or not-yet-implemented holes. Yes, a worry-wart concern.
Thanks for listening.

@LeeTibbert
Copy link
Copy Markdown
Contributor

Whilst on a many hundred kilometer drive and contemplating this PR and #4798,
it dawned on me that much of 'java.nio' ultimately funnels through 'FileChannel` and/or
'FileChannelImpl'. That might mean that many, but probably not all, virtual threads
concerns could be addressed there.

IIRC, the current implementation of 'java.net' does not funnel through 'SocketChannel'.
We may not luck out there. I'll have to check and trace when I can create some available time.

Of course, the test matrix doubles, which probably means re-writing many ancient & fragile
tests to use a 'factory' pattern, or equivalent.

Not jumping to solution, but contemplating what a solution might look like and what
evidence would need to be collected.

@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented Mar 3, 2026

Support for custom Schedulers would be a great area to explore, as combining virtual threads with IOUring would be very promising.

@lqhuang
Copy link
Copy Markdown
Contributor

lqhuang commented Mar 12, 2026

Hi @WojciechMazur,

So sorry to ping you here. Please mark this comment as off-topic to hide it after reading.

I left similar more context in the comment at bazel-contrib/rules_scala#1809 (comment), you might have already unsubscribed from that thread.

And I don't want to give up yet since the GSoC 2026 application deadline (Mar 16~30) is approaching again. So I found your most active thread recently to reach out again:


I applied for the 2025 GSoC project Scala Bazel Rules for Scala.js and Scala Native https://github.com/scalacenter/GoogleSummerOfCode?#scala-native--scalajs-projects last year. It's possible that my application might have been missed.

I resent an application letter two weeks ago for 2026 GSoC project, could you please take a look at? Don't forget to check the spam or junk folder, just in case my email is incorrectly filtered.

My sending address is lqhuang@outlook.com. And the header of two emails are

From: Lanqing Huang <lqhuang@outlook.com>
Subject: Application Letter for Project: Scala Bazel Rules for Scala.js and Scala Native
Date: July 16, 2025 at 22:47:19 GMT+8
To: wmazur@virtuslab.com

From: Lanqing Huang <lqhuang@outlook.com>
Subject: GSoC 2026 Application Letter for Project: Scala Bazel Rules for Scala.js and Scala Native
Date: February 25, 2026 at 16:23:46 GMT+8
To: wmazur@virtuslab.com

Besides, I also tried to DM you on the Discord Scala Channel last week.

Could you take a look at it when you get a chance? No hurry!

Regards
Lanqing


I've double checked your email from mutiple sources, I think I shouldn't mess up the address?

Don't forget to hide this comment once you've read it!

PS: I'm also excited to see Virtual Threads support on Scala Native!

Cheers.

@WojciechMazur WojciechMazur force-pushed the experimental/virtual-threads branch from 6e11df5 to 5e15c33 Compare April 4, 2026 18:20
@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented Apr 5, 2026

In this implementation, can we customize the Scheduler? As far as I know, this implementation integrates very well with IO_Uring.

@WojciechMazur
Copy link
Copy Markdown
Contributor Author

In this implementation, can we customize the Scheduler? As far as I know, this implementation integrates very well with IO_Uring.

There is an VirtualThreadScheduler trait that can be implemented allowing to replace default ForkJoinPool, but haven't tried to replace it with custom scheduler yet

@WojciechMazur WojciechMazur changed the title WIP: [feature]: VirtualThreads support [feature]: VirtualThreads support May 7, 2026
@WojciechMazur WojciechMazur force-pushed the experimental/virtual-threads branch from 66c880b to 83657c6 Compare May 8, 2026 09:20
@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented May 8, 2026

https://github.com/franz1981/Netty-VirtualThread-Scheduler is a nice one hope this can help scala native for high performance networking @franz1981

@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented May 8, 2026

jdk.virtualThreadScheduler.implClass https://github.com/openjdk/loom/blob/fibers/src/java.base/share/classes/java/lang/VirtualThread.java

This is needed too

@WojciechMazur WojciechMazur marked this pull request as ready for review May 8, 2026 09:48
@WojciechMazur
Copy link
Copy Markdown
Contributor Author

jdk.virtualThreadScheduler.implClass https://github.com/openjdk/loom/...

Based on the contibutions guide and the license agreements we're not to inspect the OpenJDK sources https://scala-native.org/en/latest/contrib/contributing.html#very-important-notice-about-javalib

The approach from Netty won't work for us, becouse Thread.VirtualThreadScheduler is not a public Java Thread API - instead we'd need to develop a custom interface based on SN specific scala.scalanative.runtime.VirtualThreadScheduler

@franz1981
Copy link
Copy Markdown

franz1981 commented May 8, 2026

becouse Thread.VirtualThreadScheduler is not a public Java Thread API -

Right now it is usable only in the OpenJDK fibers branch and related loom builds of Shipilev.
But there's still something similar which can be used - although not as high perf, if w Netty 4.2 i.e. eclipse-vertx/vert.x#6047

which is to run Netty's eventloops as long running virtual thread: it uses stock JDK +24 since previously NIO wasn't Loom friendly.
I hope that helped ;)

@WojciechMazur WojciechMazur force-pushed the experimental/virtual-threads branch from 5c9e73c to 643e995 Compare May 10, 2026 19:52
@WojciechMazur WojciechMazur force-pushed the experimental/virtual-threads branch from 643e995 to e3e6943 Compare May 10, 2026 19:54
@WojciechMazur WojciechMazur merged commit 6dddd1a into scala-native:main May 12, 2026
38 checks passed
@WojciechMazur WojciechMazur deleted the experimental/virtual-threads branch May 12, 2026 12:30

import scala.scalanative.annotation.alwaysinline
import scala.scalanative.libc.stdatomic.{AtomicBool, AtomicInt, AtomicLong, AtomicRef}
import scala.scalanative.libc.stdatomic.{AtomicBool, AtomicInt, AtomicLongLong, AtomicRef}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AtomicLongLong ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The universe of fancy C types:
int - typically 4 bytes
long - maybe 4, maybe 8 bytes, effectively ssize_t
long long 8 bytes

@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented May 12, 2026

Wow!

@bishabosha
Copy link
Copy Markdown

yay!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants