State of Loom

Stephane Epardaud stef at epardaud.fr
Wed May 27 15:47:39 UTC 2020


Hi

I've read about pinning and how a virtual thread is pinned if it blocks
with a native call on the stack. I've already mentioned this in the past
here, but I'm not sure this got thoroughly discussed.

A long time ago, I had to implement a cooperative thread scheduler,
where we had the equivalent of virtual threads executed on a (native)
carrier thread, in a very similar way to what Loom is describing. They
would get off the carrier when waiting for signals or when yielding on
purpose.

Those virtual threads use non-native stacks that the VM would handle, so
we did not depend on native stacks. This was all interpreted by a custom VM.

But those virtual threads could make native calls, which would initially
throw everything off the rails because we had a single carrier thread,
and native calls would push calls on the VM's stack, so it was naturally
impossible to block if you had a native call on the stack, because the
newly scheduled virtual thread would have a stack that didn't belong to it.

We solved this issue by marking native calls which can reenter the VM
specially. It was not possible to block inside the native call itself,
but only if you reentered the VM from within the native call, in which
case you had that native call on your stack.

If you did not reenter the VM, since you could not block in the native
method, you could never run into issues because you would have to exit
that native call before blocking, so this was not an issue for those
native calls.

The sort of native calls which were problematic were the reentrant ones.
And for those we never ran them on the native carrier stack/thread. We
ran them on dedicated stacks that we created especially for each virtual
thread when it called a reentrant native call.

Thus the carrier thread delegated the native call to another
native-thread/stack and blocked waiting for it to complete or block. We
would mark this with a special stack frame in the virtual thread's
stack. If it reentered the VM, the dedicated native-thread would unblock
the carrier thread which would add another mark on the virtual thread's
stack and go on executing that virtual thread.

Now, if that virtual thread would block, we could trivially switch to
another virtual thread because its stack would be free of any
native-call frame.

If it ever was scheduled again due to being unblocked, and popped back
to the special marker that indicated returning to the native call, we
would again block the carrier thread and awaken the dedicated
native-thread that had the native call, which would then probably
terminate and return back to the carrier thread.

With this technique making a native call never pinned the carrier thread
because it never happened on its native stack.

I suspect this technique can be used for virtual threads too. It will be
more expensive because it will require native thread-switching for every
native call done by a virtual thread, but again, you can mitigate this
by only doing it for reentrant native calls (not
System.currentTimeMillis() for instance), and since this is only for
virtual threads, it can be argued that it's a tradeoff that is less
harmful than pinning carrier threads.

Have you thought about doing this to solve the issue?

Cheers.



More information about the loom-dev mailing list