Continuations support in Java is an old topic. Also Sun (and later Oracle) always declined to include first-class continuations into the JVM and the Java language, the community keep trying to add this functionality via libraries and/or tools.
One of the first implementations of continuations for Java was Apache Commons JavaFlow. It was started back in 2005 (if my memory serves me well) by Torsten Curdt and was a very promising library besides some serious drawbacks. Initially the project was actively maintained, and you may find among contributors such Java heroes like Kohsuke Kawaguchi (Jenkins, Hudson and numerous other famous projects) and Eugene Kuleshov (a core ASM developer).
However, pretty soon the project was abandoned and never got out from Commons Sandbox. Some users (me included) were contributing minor patches, but fundamental issues had been never addressed until recently. Here is a list of 3 major problems the project had:
- Unacceptable performance. JavaFlow works via transforming bytecode into state machine to support continuations. To make this state machine work it has to save/restore call stack before/after every continuable method invocation. Pay attention – every continuable method. But original JavaFlow does not provide any option to mark a method as a continuable one. Hence it saves and restores stack before/after every method call. What a waste of CPU cycles!
- Java the language and Java the VM evolved, slowly but constantly. InokeDynamic, Lambdas — all of these require proper changes to bytecode modifications.
- JavaFlow tooling outdated pretty fast. Actually, command-line utility and Ant task was a step back even at the time the project was started — because it uses Maven as a build tool from the beginning.
The community concentrates mostly on the issue #3 — tools, and you may find a lot of JavaFlow Maven plugins on GitHub. That’s why I decided to share my own work that tries to address all aforementioned problems. If you ready to give JavaFlow a (second) try, then here is a quick start guide for Tascalate JavaFlow:
- Clone Git repository locally:
git clone https://github.com/vsilaev/tascalate-javaflow.git
- Run Mavne build into the parent directory of the project
mvn clean install
- Create an initial Java project for corresponding Mavne archetype
- Add the following dependency:
<dependency> <groupId>net.tascalate.javaflow</groupId> <artifactId>net.tascalate.javaflow.api</artifactId> <version>2.0-SNAPSHOT</version> </dependency>
- Add the following build plugin configuration:
<plugin> <groupId>net.tascalate.javaflow</groupId> <artifactId>net.tascalate.javaflow.tools.maven</artifactId> <version>2.0-SNAPSHOT</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>javaflow-enhance</goal> </goals> </execution> </executions> </plugin>
This plugin will instrument bytecode of classes after compilation to enable continuations support
Now we are ready to add some code. First let us start from the continuable class:
package demo; import org.apache.commons.javaflow.api.Continuation; import org.apache.commons.javaflow.api.continuable; public class Execution implements Runnable { @Override public @continuable void run() { for (int i = 1; i <= 5; i++) { System.out.println("Exe before suspend"); Object arg = Continuation.suspend(i); System.out.println("Exe after suspend: " + arg); } } }
… and the main class to invoke it:
package demo; import org.apache.commons.javaflow.api.Continuation; public class SimpleExample { public static void main(final String[] argv) { final String[] strings = {"A", "B", "C"}; for ( Continuation cc = Continuation.startWith(new Execution()); null != cc; ) { final int retVal = (Integer)cc.value(); System.out.println("Interrupted " + retVal); // Let's continuation resume cc = cc.resume( strings[retVal % strings.length] ); } System.out.println("ALL DONE"); } }
Build project with Maven and run demo.SimpleExample
. The following output should be produced:
Exe before suspend Interrupted 1 Exe after suspend: B Exe before suspend Interrupted 2 Exe after suspend: C Exe before suspend Interrupted 3 Exe after suspend: A Exe before suspend Interrupted 4 Exe after suspend: B Exe before suspend Interrupted 5 Exe after suspend: C ALL DONE
Now we are ready to review the code
Execution
class is almost a regular implementation of thejava.lang.Runnable
interface. “Almost” because its methodrun
is annotated with@continuable
annotation. This is where the magic begins. This annotation tells JavaFlow tools to instrument a method body and rewrite it with continuations support. Namely, a state machine will be created to allow multiple method’s enter/exit points, and stack will be captured/restored around invocation of any other continuable methods, in this case —Continuation.suspend(...)
- Main method of the
SimpleExample
class creates new continuation from the Runnable object and starts it immediately:Continuation.startWith(new Execution())
Execution.run
method is started and executed till theContinuation.suspend
call. At this pointExecution.run
method ends, and the value passed as an argument toContinuation.suspend
is returned to caller (variableretVal
). So method with avoid
return type produces an Integer result. The magic continues.- After printing value received from the continuation, the main method resumes a continuation via the call to the
Continuation.resume(...)
Execution.run
method starts again, however this time it’s not running from the beginning, but rather from the point where a call to theContinuation.suspend(...)
returns, right insight the “for” loop. A value received here is exactly the string we passed outside via callingContinuation.resume(...)
. The magic runs in a loop.- On the next iteration
Execution.run
again met a call to theContinuation.suspend
. We are back to the main method again. - The process repeats till we end looping in the
Execution.run
method. This time the method returns normally (without continuation) and we get anull
continuation in the main method. We are done.
Next time I will explain in more details what options are possible to declare a method as continuable, but examples for this topic are readily available in Tascalate JavaFlow repository at GitHub.