// HACKER NEWS — CYBERSECURITY
Virtual Threads for a scripting language in Java 8 without Loom
Jactl is a secure, embeddable scripting language for Java applications.
When I first started developing Jactl, I wanted a scripting language that compiled to bytecode for
optimum performance, was secure so applications could control exactly what scripts could
and couldn't do, and most of all, did not block the execution thread when long-running, blocking operations
were performed.
When I began developing Jactl, Java 21 and Virtual Threads did not exist, and event-driven,
reactive applications (such as ones based on Vert.x) were the way in which
high-throughput Java applications were written.
I also had a need for a scripting language that would work on applications still stuck on Java 8
or Java 11.
Now, with the later versions of Java, Java applications that want to use
Virtual Threads rather than adopting an event-based architecture can choose to disable
the built-in Jactl async mechanism described here by configuring
the JactlContext.async(false) flag.
A reactive application is one where a pool of event-loop threads process events from a queue.
The golden rule is that events should never block as this will pause one of the event-loop threads,
preventing it from processing any more events until that blocking operation completes.
A blocking operation is one where the thread is no longer actively processing code but is waiting
for the result of an operation such as a database request or a remote procedure call.
If blocking operations can occur on an event-loop thread, you will eventually see situations where
all threads are waiting for long-running operations and no events are being processed.
I wanted a scripting language that could be invoked from an event-loop thread but, when it
performed any blocking operation, it would somehow save its state and return, freeing up the
thread to process further events.
When the result of the long-running operation was available the script would be resumed from the
point where it left off and continue with its processing.
In Java 21 and later, Virtual Threads provide the same functionality - they preserve the call stack
with all the local variables and allow the thread to continue performing other work and when the
blocking operation is complete the call stack is restored and the program continues from where it
left off.
The goal was only to save the execution state of the Jactl code, not the state of the Java
code that was invoking a Jactl script.
Since the Java application is event-based, the script will complete as a new event on an
event-loop thread and a completion callback will be invoked once the script
finishes that calls back into the Java application with the script result.
The callback provided by the application can hold onto any state that the application needs.
In Java 8, of course, there is no way to preserve the call stack, either in Java or in JVM
bytecode, so I had to use a different mechanism to achieve the same end.
Imagine that we have a script that needs to invoke a function (or method) that performs a
long-running operation.
For the sake of the example, let's assume that the function needs to perform sleep() for some
period of time before it does something else.
There will be a Java call stack with a stack frame for each nested method call and then the rest
of the stack will be Jactl stack frames, one for each nested Jactl function call, with
the topmost stack frame being the stack frame for the sleep() function itself.
Each stack frame tracks where in the code the function invocation occurs, along with the
values for its local variables:
To capture the execution state, I figured that the easiest thing to do would be to throw an exception at
the start of a long-running operation like sleep() and generate code in each Jactl
method/function that catches the exception, saves its state, and throws a new exception that is
chained to the one it just caught.
I called the class for the exception being thrown Continuation since a continuation
is a representation of the