Last active
April 30, 2024 19:55
-
-
Save thomasdarimont/bd22bbce165334dc7fa5ccf28c589414 to your computer and use it in GitHub Desktop.
Java21 Continuations Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo.cont; | |
import java.lang.invoke.MethodHandle; | |
import java.lang.invoke.MethodHandles; | |
import java.lang.invoke.MethodType; | |
import java.util.function.Consumer; | |
public final class Continuation { | |
private final Object delegate; | |
private static final Class<?> IMPL_CLASS; | |
private static final MethodHandle NEW; | |
private static final MethodHandle YIELD; | |
private static final MethodHandle RUN; | |
private static final MethodHandle IS_DONE; | |
static { | |
try { | |
IMPL_CLASS = Class.forName("jdk.internal.vm.Continuation"); | |
var lookup = MethodHandles.privateLookupIn(IMPL_CLASS, MethodHandles.lookup()); | |
NEW = lookup.findConstructor(IMPL_CLASS, MethodType.methodType(void.class, Scope.IMPL_CLASS, Runnable.class)); | |
RUN = lookup.findVirtual(IMPL_CLASS, "run", MethodType.methodType(void.class)); | |
YIELD = lookup.findStatic(IMPL_CLASS, "yield", MethodType.methodType(boolean.class, Scope.IMPL_CLASS)); | |
IS_DONE = lookup.findVirtual(IMPL_CLASS, "isDone", MethodType.methodType(boolean.class)); | |
} catch (Throwable t) { | |
throw new ExceptionInInitializerError(t); | |
} | |
} | |
public <T> Continuation(String scopeName, Consumer<Scope<T>> code) { | |
this(scope(scopeName), code); | |
} | |
public <T> Continuation(Scope<T> scope, Consumer<Scope<T>> code) { | |
try { | |
this.delegate = NEW.invoke(scope.delegate, (Runnable) () -> code.accept(scope)); | |
} catch (Throwable t) { | |
throw new RuntimeException(t); | |
} | |
} | |
public void run() { | |
try { | |
RUN.invoke(delegate); | |
} catch (Throwable t) { | |
throw new RuntimeException(t); | |
} | |
} | |
public <T> T next(Scope<T> scope) { | |
run(); | |
return getState(scope); | |
} | |
public static <T> void yield(Scope<T> scope) { | |
try { | |
YIELD.invoke(scope.delegate); | |
} catch (Throwable t) { | |
throw new RuntimeException(t); | |
} | |
} | |
public static <T> void yield(Scope<T> scope, T state) { | |
ScopedValue.where(scope.state, state).run(() -> { | |
try { | |
YIELD.invoke(scope.delegate); | |
} catch (Throwable t) { | |
throw new RuntimeException(t); | |
} | |
}); | |
} | |
public static <T> T getState(Scope<T> scope) { | |
return scope.state.get(); | |
} | |
public boolean isDone() { | |
try { | |
return (boolean) IS_DONE.invoke(delegate); | |
} catch (Throwable t) { | |
throw new RuntimeException(t); | |
} | |
} | |
public static <T> Scope<T> scope(String name) { | |
return new Scope<>(name); | |
} | |
public static final class Scope<T> { | |
private final Object delegate; | |
private final ScopedValue<T> state; | |
private static final Class<?> IMPL_CLASS; | |
private static final MethodHandle NEW; | |
static { | |
try { | |
IMPL_CLASS = Class.forName("jdk.internal.vm.ContinuationScope"); | |
var lookup = MethodHandles.privateLookupIn(IMPL_CLASS, MethodHandles.lookup()); | |
NEW = lookup.findConstructor(IMPL_CLASS, MethodType.methodType(void.class, String.class)); | |
} catch (Throwable t) { | |
throw new ExceptionInInitializerError(t); | |
} | |
} | |
private Scope(String name) { | |
try { | |
this.delegate = NEW.invoke(name); | |
this.state = ScopedValue.newInstance(); | |
} catch (Throwable e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@Override | |
public String toString() { | |
return delegate.toString(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo.cont; | |
import java.util.Scanner; | |
/** | |
* Run on JDK 21+ with <code>--add-opens java.base/jdk.internal.vm=ALL-UNNAMED --enable-preview</code> | |
*/ | |
public class ContinuationDemo { | |
static void countUp(Continuation.Scope<?> scope) { | |
for (int i = 0; i < 10; i++) { | |
System.out.println(i); | |
Continuation.yield(scope); // gives up control until continuation is resumed | |
} | |
System.out.println("Done"); | |
} | |
public static void main(String[] args) { | |
var continuation = new Continuation("myscope", ContinuationDemo::countUp); | |
try (var scanner = new Scanner(System.in)) { | |
while (!continuation.isDone()) { // continuation code block is not finished | |
System.out.print("Press enter to run one more step: "); | |
scanner.nextLine(); | |
continuation.run(); // resume continuation | |
} | |
System.out.println("No more input."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo.cont; | |
import java.util.Scanner; | |
/** | |
* Run on JDK 21+ with <code>--add-opens java.base/jdk.internal.vm=ALL-UNNAMED --enable-preview</code> | |
*/ | |
public class ContinuationDemo2 { | |
static void countUp(Continuation.Scope<?> scope) { | |
for (int i = 0; i < 10; i++) { | |
System.out.printf("%s: %s%n", scope, i); | |
Continuation.yield(scope); // gives up control until continuation is resumed | |
} | |
System.out.printf("%s: Done%n", scope); | |
} | |
public static void main(String[] args) { | |
var counter1 = new Continuation("counter1", ContinuationDemo2::countUp); | |
var counter2 = new Continuation("counter2", ContinuationDemo2::countUp); | |
try (var scanner = new Scanner(System.in)) { | |
loop: | |
while (!counter1.isDone() || !counter2.isDone()) { // continuation code blocks are not finished | |
System.out.print("Enter 1 or 2 to select counter to increment, or 0 to exit: "); | |
int counter = scanner.nextInt(); | |
switch (counter) { | |
case 1: | |
if (!counter1.isDone()) { | |
counter1.run(); // throws "IllegalStateException: Continuation terminated" if already completed | |
} | |
break; | |
case 2: | |
if (!counter2.isDone()) { | |
counter2.run(); | |
} | |
break; | |
case 0: | |
break loop; | |
} | |
} | |
System.out.println("No more input."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package demo.cont; | |
import java.util.Scanner; | |
/** | |
* Run on JDK 21+ with <code>--add-opens java.base/jdk.internal.vm=ALL-UNNAMED --enable-preview</code> | |
*/ | |
public class ContinuationDemo3 { | |
static void generator(Continuation.Scope<Integer> scope) { | |
for (int i = 0; ; i++) { | |
Continuation.yield(scope, i); | |
} | |
} | |
public static void main(String[] args) { | |
var scope = Continuation.<Integer>scope("counter"); | |
var counter = new Continuation(scope, ContinuationDemo3::generator); | |
try (var scanner = new Scanner(System.in)) { | |
while (!counter.isDone()) { | |
scanner.nextLine(); | |
int value = counter.next(scope); | |
System.out.printf("State: %s%n", value); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I added
implements Iterable<T>
toContinuation
if anyone else wants something similar:Usage is like:
Implementation: