-
Notifications
You must be signed in to change notification settings - Fork 16
Continuations
The original RIFE framework made a name for itself as the first Java web
framework that provided continuations. RIFE2 revised the continuations engine
to support invokedynamic and StackMapTable, offering support for the latest
JVM and JDK versions.
To this day, RIFE2 remains the only Java framework that natively provides continuations in its web engine. With Project Loom researching how to provide continuations directly into the JVM, interest in this technology seems to be re-invigorated.
Continuations basically contain all information about a specific program location and local variables, allowing you to stop execution and to later continue from a previous spot with the same local state available. It's like the active thread can complete shut down and not run anymore, then be started up again from the same spot with all the state available, as if nothing changed.
Continuations can be very useful in web application development because they leverage the natural flow of your code to express the state that needs to be captured and restored between HTTP requests.
A typical example is form entry where the first request displays the form, and the second request handles the form submission.
A traditional web application could structure that this way:
-> REQUEST GET https://host/yourpage
execute logic to determine state
use state to render form
<- RESPONSE
-> REQUEST POST https://host/yourpage
execute logic again the determine state
detect that a form was submitted
extract state from form
validate submitted state with previous state
handle form
<- RESPONSE
With continuations, this can be restructured in a more natural way:
-> REQUEST GET https://host/yourpage
execute logic to determine state
use state to render form
pause
<- RESPONSE
-> REQUEST POST https://host/yourpage
resume
handle form
<- RESPONSE
Whenever you want your element's execution to pause, you can use the c.pause()
method. This will stop the processing of your element and send the HTTP response back.
For example:
public class Form implements Element {
@Parameter String name; // field annotation captures form data
public void process(Context c) {
var t = c.template("HelloForm"); // create a new template instance
t.setBlock("content", "form"); // display the form
c.print(t); // print the template
c.pause(); // create continuation and send response
// <--
// this is where the form submission will automatically resume,
// restoring the continuation, along with its state (the template)
// none of the logic above this point will be executed when the
// continuation resumes
t.setValueEncoded("name", name); // display name submitted through form
t.setBlock("content", "greeting"); // display the greeting with the name
c.print(t); // print the template
}
}Some template engine features are used in the example above that are explained later, but I hope it will bring the concept of continuations across. You can find the full example in the RIFE2 repository.
IMPORTANT : Continuations are only available in element classes and not in element lambdas since RIFE2 needs to be able to create new instances of the elements.
You can continue using all of Java's language features together with continuations.
For example this simple counter, uses a while loop and a local count
variable:
public class Counter implements Element {
public void process(Context c) {
var count = 0; // create and initialize the count variable
while (count++ < 10) { // continue looping until it has reached 10
c.print(count); // display the count and a link to increase it
c.print(" <a href='" + c.urlFor(c.route()) + "'>add</a>");
c.pause(); // create continuation and send response
// <--
// this is where the link will automatically resume,
// restoring the continuation and iterating through the while
// loop again
}
c.print("done"); // count is larger than 10, print done
}
}TIP : You can find this counter example in the RIFE2 GitHub repository.
The RIFE2 source repository also provides a more involved example, that implements a number guessing game with continuations in a single element.
NOTE : Continuations have a limited lifespan, when it takes too long to resume them, RIFE2 will automatically purge them. A continuation's maximum duration and the purge task frequency can be configured through
RifeConfig.engine().
Only the process(Context) method of your element is instrumented for
continuations. You can call other methods, but those will not be able to use
the pause() method.
Each execution through the process method only has one active continuation,
which gets a unique ID. This ID changes every time the continuation is resumed.
Usage of previous IDs will start your element logic from the beginning again.
When you use RIFE2 c.urlFor(route) method or the route: filtered template
value tag to generate your URLs, RIFE2 will automatically add a contId request
parameter when necessary. You can also manually retrieve the active continuation
ID through c.continuationId() or tell RIFE2 to generate a contID query
parameter anywhere by using the context:contId template value tag.
Continuations are not possible without advanced bytecode manipulation. In order
to enable continuations support, it's necessary to launch your JVM with the
RIFE2 agent jar. This is distributed with the regular Maven artifact and uses
the agent classifier.
If you use bld, activating the agent is easy, just call useRife2Agent(version) in
your project“ file:
public class MyappBuild extends WebProject {
public MyappBuild() {
useRife2Agent(version(1,8,0));
//...
}
}The RIFE2 Gradle plugin also has support for activating the agent, simply edit
app/build.gradle.kts and enable the option:
//...
rife2 {
version.set("1.8.0")
uberMainClass.set("hello.AppUber")
useAgent.set(true)
}
//...NOTE : If RIFE2's instrumentation agent isn't present, your element will still be processed by the web engine, but instead of executing the
c.pause()call as expected, it will trigger an exception that indicates instrumentation is missing.
Next learn more about Out-of-container Testing