Valery Silaev's Blog

if it ain't broken we'll break it

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:

  1. 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!
  2. Java the language and Java the VM evolved, slowly but constantly. InokeDynamic, Lambdas — all of these require proper changes to bytecode modifications.
  3. 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:

  1. Clone Git repository locally:
    git clone https://github.com/vsilaev/tascalate-javaflow.git
    
  2. Run Mavne build into the parent directory of the project
    mvn clean install
    
  3. Create an initial Java project for corresponding Mavne archetype
  4. Add the following dependency:
    <dependency>
        <groupId>net.tascalate.javaflow</groupId>
        <artifactId>net.tascalate.javaflow.api</artifactId>
        <version>2.0-SNAPSHOT</version>
    </dependency>
    
  5. 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

  1. Execution class is almost a regular implementation of the java.lang.Runnable interface. “Almost” because its method run 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(...)
  2. Main method of the SimpleExample class creates new continuation from the Runnable object and starts it immediately: Continuation.startWith(new Execution())
  3. Execution.run method is started and executed till the Continuation.suspend call. At this point Execution.run method ends, and the value passed as an argument to Continuation.suspend is returned to caller (variable retVal). So method with a void return type produces an Integer result. The magic continues.
  4. After printing value received from the continuation, the main method resumes a continuation via the call to the Continuation.resume(...)
  5. Execution.run method starts again, however this time it’s not running from the beginning, but rather from the point where a call to the Continuation.suspend(...) returns, right insight the “for” loop. A value received here is exactly the string we passed outside via calling Continuation.resume(...). The magic runs in a loop.
  6. On the next iteration Execution.run again met a call to the Continuation.suspend. We are back to the main method again.
  7. The process repeats till we end looping in the Execution.run method. This time the method returns normally (without continuation) and we get a null 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: