├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md ├── loom is looming.odp ├── loom is looming.pdf ├── old ├── main │ └── java │ │ └── fr │ │ └── umlv │ │ └── loom │ │ ├── Context.java │ │ ├── EventContinuation.java │ │ ├── Generators.java │ │ ├── HelloContinuation.java │ │ ├── ProducerConsumer.java │ │ ├── Pythagorean.java │ │ ├── Server.java │ │ ├── Task.java │ │ ├── actor │ │ ├── ALotOfMessagesDemo.java │ │ ├── Actor.java │ │ ├── Case.java │ │ ├── CounterActorDemo.java │ │ ├── CounterStringActorDemo.java │ │ └── CounterStringActorExprSwitchDemo.java │ │ ├── example │ │ ├── Example1.java │ │ ├── Example2.java │ │ ├── Example3.java │ │ ├── Example4.java │ │ └── Example5.java │ │ ├── fiberactor │ │ └── Actors.java │ │ ├── preempt │ │ └── Main.java │ │ ├── proxy │ │ ├── .gitignore │ │ ├── TCPClassicalSocketFiberProxy.java │ │ ├── TCPContinuationProxy.java │ │ ├── TCPFiberProxy.java │ │ └── TCPThreadProxy.java │ │ ├── scheduler │ │ ├── BarrierExample.java │ │ ├── ExchangerExample.java │ │ ├── FairScheduler.java │ │ ├── FifoScheduler.java │ │ ├── InterruptibleScheduler.java │ │ ├── LockExample.java │ │ ├── RandomScheduler.java │ │ ├── Scheduler.java │ │ ├── SchedulerImpl.java │ │ ├── SemaphoreExample.java │ │ └── WorkQueueExample.java │ │ └── test │ │ ├── JayTest.java │ │ └── Main.java └── test │ └── java │ └── fr │ └── umlv │ └── loom │ ├── ContextTests.java │ ├── EventContinuationTests.java │ ├── FileDirs.java │ ├── GeneratorsTests.java │ └── TaskTests.java ├── pom.xml ├── scoop about scopes.odp ├── scoop about scopes.pdf ├── src ├── main │ └── java │ │ └── fr │ │ └── umlv │ │ └── loom │ │ ├── continuation │ │ ├── Continuation.java │ │ └── ContinuationMain.java │ │ ├── example │ │ ├── _10_future_state_and_shutdown.java │ │ ├── _11_shutdown_on_success.java │ │ ├── _12_shutdown_on_failure.java │ │ ├── _13_timeout.java │ │ ├── _14_http_server.java │ │ ├── _15_adhoc_scope.java │ │ ├── _1_starting_thread.java │ │ ├── _21_shutdown_on_success.java │ │ ├── _22_shutdown_on_failure.java │ │ ├── _23_as_stream.java │ │ ├── _24_as_stream_short_circuit.java │ │ ├── _2_thread_builder.java │ │ ├── _3_how_many_platform_thread.java │ │ ├── _4_how_many_virtual_thread.java │ │ ├── _5_continuation.java │ │ ├── _6_thread_local.java │ │ ├── _7_scoped_value.java │ │ ├── _8_executor.java │ │ └── _9_structured_concurrency.java │ │ ├── executor │ │ ├── UnsafeExecutors.java │ │ └── VirtualThreadExecutor.java │ │ ├── oldstructured │ │ ├── AsyncScope2.java │ │ └── AsyncScope2Impl.java │ │ ├── proxy │ │ ├── Host.java │ │ ├── TCPPlatformThreadProxy.java │ │ └── TCPVirtualThreadProxy.java │ │ ├── reducer │ │ ├── StructAsyncScopeDemo.java │ │ └── StructuredAsyncScope.java │ │ ├── structured │ │ ├── Invokable.java │ │ ├── StructuredScopeAsStream.java │ │ ├── StructuredScopeShutdownOnFailure.java │ │ └── StructuredScopeShutdownOnSuccess.java │ │ └── tutorial │ │ └── RickAndMortyExample.java └── test │ └── java │ └── fr │ └── umlv │ └── loom │ ├── continuation │ └── ContinuationTest.java │ ├── executor │ ├── UnsafeExecutorsTest.java │ └── VirtualThreadExecutorTest.java │ ├── oldstructured │ ├── AsyncScope2ExampleTest.java │ └── AsyncScope2Test.java │ └── structured │ ├── StructuredScopeAsStreamTest.java │ ├── StructuredScopeShutdownOnFailureTest.java │ └── StructuredScopeShutdownOnSuccessTest.java └── todo.html /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | schedule: 7 | - cron: '0 7 * * *' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | java: [ '21' ] 15 | name: Java ${{ matrix.Java }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: setup 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: zulu 22 | java-version: ${{ matrix.java }} 23 | - name: build 24 | run: | 25 | mvn -B package 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | /target/ 26 | 27 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=14 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore 13 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 14 | org.eclipse.jdt.core.compiler.release=disabled 15 | org.eclipse.jdt.core.compiler.source=1.3 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rémi Forax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loom-fiber 2 | This repository contains both my experimentation of the OpenJDK project Loom prototype 3 | and a presentation and examples showing how to use it. 4 | 5 | There is a [presentation of the loom project](loom%20is%20looming.pdf) 6 | wiht the [examples](src/main/java/fr/umlv/loom/example). 7 | 8 | ## How to build 9 | 10 | Download the latest early access build of jdk-20 [http://jdk.java.net/](http://jdk.java.net/) 11 | set the environment variable JAVA_HOME to point to that JDK and then use Maven. 12 | 13 | ``` 14 | export JAVA_HOME=/path/to/jdk 15 | mvn package 16 | ``` 17 | 18 | ## How to run the examples 19 | 20 | On top of each example, there is the command line to run it. 21 | - Loom is a preview version so `--enable-preview` is required, 22 | - For `ScopeLocal` and `StructuredTaskScope`, these are not yet part of the official API 23 | and are declared in module/package `jdk.incubator.concurrent`so this module 24 | has to be added to the command line with `--add-modules jdk.incubator.concurrent`, 25 | - If you want to play with the internals, the class `Continuation` is hidden thus 26 | `--add-exports java.base/jdk.internal.vm=ALL-UNNAMED` should be added to the command line. 27 | 28 | ## AsyncScope 29 | 30 | This repository also contains a high-level structured concurrency construct named 31 | [src/main/java/fr/umlv/loom/structured/AsyncScope.java](AsyncScope) that is a proposed replacement 32 | to the more low level `StructuredTaskScope` currently provided by the OpenJDK loom repository. 33 | 34 | ## Loom actor framework 35 | 36 | At some point of the history, this project was containing an actor system based on loom. 37 | It has now its own repository [https://github.com/forax/loom-actor](https://github.com/forax/loom-actor). 38 | 39 | ## Loom expressjs 40 | 41 | There is a re-implementation of the [expressjs API](https://expressjs.com/en/4x/api.html) in Java using Loom 42 | [JExpressLoom.java](https://github.com/forax/jexpress/blob/master/src/main/java/JExpressLoom.java). 43 | -------------------------------------------------------------------------------- /loom is looming.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/loom-fiber/317ce459a2152467625eb9dc2db2120136d6cd3a/loom is looming.odp -------------------------------------------------------------------------------- /loom is looming.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/loom-fiber/317ce459a2152467625eb9dc2db2120136d6cd3a/loom is looming.pdf -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/Context.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.function.Supplier; 6 | 7 | public class Context { 8 | private final ContinuationScope scope = new ContinuationScope("context"); 9 | private final Supplier initialSupplier; 10 | 11 | static class ContextContinuation extends Continuation { 12 | private V value; 13 | 14 | public ContextContinuation(ContinuationScope scope, Runnable runnable) { 15 | super(scope, runnable); 16 | } 17 | } 18 | 19 | public Context(Supplier initialSupplier) { 20 | this.initialSupplier = initialSupplier; 21 | } 22 | 23 | public V getValue() { 24 | @SuppressWarnings("unchecked") 25 | var continuation = (ContextContinuation)Continuation.getCurrentContinuation(scope); 26 | if (continuation == null) { 27 | throw new IllegalStateException(); 28 | } 29 | var value = continuation.value; 30 | if (value == null) { 31 | return continuation.value = requireNonNull(initialSupplier.get()); 32 | } 33 | return value; 34 | } 35 | public void setValue(V value) { 36 | requireNonNull(value); 37 | @SuppressWarnings("unchecked") 38 | var continuation = (ContextContinuation)Continuation.getCurrentContinuation(scope); 39 | if (continuation == null) { 40 | throw new IllegalStateException(); 41 | } 42 | continuation.value = value; 43 | } 44 | 45 | public void enter(Runnable runnable) { 46 | new ContextContinuation<>(scope, runnable).run(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/EventContinuation.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import java.util.Objects; 4 | import java.util.function.BiFunction; 5 | 6 | public class EventContinuation { 7 | private static final ContinuationScope SCOPE = new ContinuationScope("event"); 8 | 9 | private final Continuation continuation; 10 | private P parameter; 11 | private R yieldValue; 12 | 13 | public interface Yielder { 14 | P yield(R yieldValue); 15 | } 16 | 17 | public EventContinuation(BiFunction, ? super P, ? extends R> consumer) { 18 | Objects.requireNonNull(consumer); 19 | continuation = new Continuation(SCOPE, () -> { 20 | this.yieldValue = consumer.apply(yieldValue -> { 21 | this.yieldValue = yieldValue; 22 | Continuation.yield(SCOPE); 23 | P parameter = this.parameter; 24 | this.parameter = null; 25 | return parameter; 26 | }, parameter); 27 | }); 28 | } 29 | 30 | public R execute(P parameter) { 31 | this.parameter = parameter; 32 | continuation.run(); 33 | R yieldValue = this.yieldValue; 34 | this.yieldValue = null; 35 | return yieldValue; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/Generators.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | import java.util.Objects; 6 | import java.util.Spliterator; 7 | import java.util.function.Consumer; 8 | import java.util.stream.Stream; 9 | import java.util.stream.StreamSupport; 10 | 11 | public class Generators { 12 | /*private*/ static final ContinuationScope SCOPE = new ContinuationScope("generators"); 13 | 14 | public static Iterator iterator(Consumer> consumer) { 15 | Objects.requireNonNull(consumer); 16 | return new Iterator<>() { 17 | private final Continuation continuation = new Continuation(SCOPE, () -> { 18 | consumer.accept(value -> { 19 | element = Objects.requireNonNull(value); 20 | Continuation.yield(SCOPE); 21 | }); 22 | }); 23 | private T element; 24 | 25 | @Override 26 | public boolean hasNext() { 27 | if (element != null) { 28 | return true; 29 | } 30 | if (continuation.isDone()) { 31 | return false; 32 | } 33 | continuation.run(); 34 | return element != null; 35 | } 36 | 37 | @Override 38 | public T next() { 39 | if (!hasNext()) { 40 | throw new NoSuchElementException(); 41 | } 42 | var element = this.element; 43 | this.element = null; 44 | return element; 45 | } 46 | }; 47 | } 48 | 49 | public static Stream stream(Consumer> consumer) { 50 | Objects.requireNonNull(consumer); 51 | return StreamSupport.stream(new Spliterator<>() { 52 | private final Continuation continuation = new Continuation(SCOPE, () -> { 53 | consumer.accept(value -> { 54 | Objects.requireNonNull(value); 55 | this.action.accept(value); 56 | Continuation.yield(SCOPE); 57 | }); 58 | }); 59 | private Consumer action; 60 | 61 | @Override 62 | public Spliterator trySplit() { 63 | return null; 64 | } 65 | @Override 66 | public boolean tryAdvance(Consumer action) { 67 | if (continuation.isDone()) { 68 | return false; 69 | } 70 | this.action = action; 71 | try { 72 | continuation.run(); 73 | } finally { 74 | this.action = null; 75 | } 76 | return true; 77 | } 78 | @Override 79 | public int characteristics() { 80 | return IMMUTABLE | NONNULL; 81 | } 82 | @Override 83 | public long estimateSize() { 84 | return Long.MAX_VALUE; 85 | } 86 | }, false); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/HelloContinuation.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | public class HelloContinuation { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("hello"); 6 | var cont = new Continuation(scope, () -> { 7 | System.out.println("hello"); 8 | 9 | Continuation.yield(scope); 10 | System.out.println("hello 2"); 11 | 12 | }); 13 | 14 | cont.run(); 15 | System.out.println("i'm back"); 16 | cont.run(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/ProducerConsumer.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.Executors; 6 | 7 | public class ProducerConsumer { 8 | private static void produce(BlockingQueue queue) { 9 | try { 10 | for(;;) { 11 | queue.put("hello "); 12 | System.out.println("produce " + Thread.currentThread()); 13 | 14 | Thread.sleep(200); 15 | } 16 | } catch (InterruptedException e) { 17 | throw new AssertionError(e); 18 | } 19 | } 20 | 21 | private static void consume(BlockingQueue queue) { 22 | try { 23 | for(;;) { 24 | var message = queue.take(); 25 | System.out.println("consume " + Thread.currentThread() + " received " + message); 26 | 27 | Thread.sleep(200); 28 | } 29 | } catch (InterruptedException e) { 30 | throw new AssertionError(e); 31 | } 32 | } 33 | 34 | public static void main(String[] args) { 35 | var queue = new ArrayBlockingQueue(8192); 36 | 37 | // try (var scheduler = Executors.newFixedThreadPool(1)) { 38 | // var factory = 39 | //Thread.builder().virtual(scheduler).factory(); 40 | // try (var executor = 41 | //Executors.newUnboundedExecutor(factory)) { 42 | // executor.submit(() -> produce(queue)); 43 | // executor.submit(() -> consume(queue)); 44 | // } 45 | // } 46 | 47 | var scheduler = Executors.newFixedThreadPool(1); 48 | Thread.builder().name("producer").virtual(scheduler).task(() -> produce(queue)).build().start(); 49 | Thread.builder().name("consumer").virtual(scheduler).task(() -> consume(queue)).build().start(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/Pythagorean.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | import java.util.function.Consumer; 6 | 7 | public class Pythagorean { 8 | static Iterator iterator(Consumer> consumer) { 9 | return new Iterator<>() { 10 | private T value; 11 | private final Continuation continuation; 12 | 13 | { 14 | var scope = new ContinuationScope("generator"); 15 | continuation = new Continuation(scope, () -> { 16 | consumer.accept(v -> { 17 | value = v; 18 | Continuation.yield(scope); 19 | }); 20 | }); 21 | continuation.run(); 22 | } 23 | 24 | public boolean hasNext() { 25 | return !continuation.isDone(); 26 | } 27 | public T next() { 28 | if (!hasNext()) { throw new NoSuchElementException(); } 29 | var value = this.value; 30 | continuation.run(); 31 | return value; 32 | } 33 | }; 34 | } 35 | 36 | public static void main(String[] args) { 37 | class Triple { 38 | private final int x, y, z; Triple(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } 39 | public @Override String toString() { return "(" + x + ", " + y + ", " + z + ')'; } 40 | } 41 | 42 | Iterable iterable = () -> iterator(yielder -> { 43 | for(var z = 1; z <= 100; z++) { 44 | for(var x = 1; x <= z; x++) { 45 | for(var y = x; y <= z; y++) { 46 | if (x*x + y * y == z * z) { 47 | yielder.accept(new Triple(x, y, z)); 48 | } 49 | } 50 | } 51 | } 52 | }); 53 | 54 | for(var triple: iterable) { 55 | System.out.println(triple); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/Server.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static java.nio.charset.StandardCharsets.ISO_8859_1; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | import java.net.InetSocketAddress; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.SelectionKey; 10 | import java.nio.channels.Selector; 11 | import java.nio.channels.ServerSocketChannel; 12 | import java.nio.channels.SocketChannel; 13 | import java.nio.charset.Charset; 14 | import java.nio.file.Files; 15 | import java.nio.file.NoSuchFileException; 16 | import java.nio.file.Path; 17 | 18 | public class Server { 19 | static final ContinuationScope SCOPE = new ContinuationScope("server"); 20 | 21 | public interface IO { 22 | int read(ByteBuffer buffer) throws IOException; 23 | void write(ByteBuffer buffer) throws IOException; 24 | 25 | default String readLine(ByteBuffer buffer, Charset charset) throws IOException { 26 | int read; 27 | loop: while((read = read(buffer)) != -1) { 28 | buffer.flip(); 29 | while(buffer.hasRemaining()) { 30 | if(buffer.get() == '\n') { 31 | break loop; 32 | } 33 | } 34 | if (buffer.position() == buffer.capacity()) { 35 | throw new IOException("string too big"); 36 | } 37 | buffer.limit(buffer.capacity()); 38 | } 39 | if (read == -1) { 40 | return null; 41 | } 42 | buffer.flip(); 43 | byte[] array = new byte[buffer.limit() - 2]; 44 | buffer.get(array); 45 | buffer.get(); // skip '\n' 46 | return new String(array, charset); 47 | } 48 | 49 | default void write(String text, Charset charset) throws IOException { 50 | write(ByteBuffer.wrap(text.getBytes(charset))); 51 | } 52 | } 53 | 54 | public interface IOConsumer { 55 | void accept(ByteBuffer buffer, IO io) throws IOException; 56 | } 57 | 58 | 59 | private static void closeUnconditionally(Closeable closeable) { 60 | try { 61 | closeable.close(); 62 | } catch (IOException e) { 63 | // do nothing 64 | } 65 | } 66 | 67 | private static void mainLoop(IOConsumer consumer, int localPort) throws IOException { 68 | var server = ServerSocketChannel.open(); 69 | server.configureBlocking(false); 70 | server.bind(new InetSocketAddress(localPort)); 71 | var selector = Selector.open(); 72 | var acceptContinuation = new Continuation(SCOPE, () -> { 73 | for(;;) { 74 | SocketChannel channel; 75 | try { 76 | channel = server.accept(); 77 | } catch (IOException e) { 78 | System.err.println(e.getMessage()); 79 | closeUnconditionally(server); 80 | closeUnconditionally(selector); 81 | return; 82 | } 83 | SelectionKey key; 84 | try { 85 | channel.configureBlocking(false); 86 | key = channel.register(selector, 0); 87 | } catch (IOException e) { 88 | System.err.println(e.getMessage()); 89 | closeUnconditionally(channel); 90 | return; 91 | } 92 | 93 | var continuation = new Continuation(SCOPE, () -> { 94 | var buffer = ByteBuffer.allocateDirect(8192); 95 | var io = new IO() { 96 | @Override 97 | public int read(ByteBuffer buffer) throws IOException { 98 | int read; 99 | while ((read = channel.read(buffer)) == 0) { 100 | key.interestOps(SelectionKey.OP_READ); 101 | Continuation.yield(SCOPE); 102 | } 103 | key.interestOps(0); 104 | return read; 105 | } 106 | 107 | @Override 108 | public void write(ByteBuffer buffer) throws IOException { 109 | while(buffer.hasRemaining()) { 110 | while (channel.write(buffer) == 0) { 111 | key.interestOps(SelectionKey.OP_WRITE); 112 | Continuation.yield(SCOPE); 113 | } 114 | } 115 | key.interestOps(0); 116 | } 117 | }; 118 | try { 119 | consumer.accept(buffer, io); 120 | } catch (IOException|RuntimeException e) { 121 | e.printStackTrace(); 122 | } finally { 123 | key.cancel(); 124 | closeUnconditionally(channel); 125 | } 126 | }); 127 | key.attach(continuation); 128 | continuation.run(); 129 | Continuation.yield(SCOPE); 130 | } 131 | }); 132 | server.register(selector, SelectionKey.OP_ACCEPT, acceptContinuation); 133 | for(;;) { 134 | selector.select(key -> ((Continuation)key.attachment()).run()); 135 | } 136 | } 137 | 138 | public static void main(String[] args) throws IOException { 139 | System.out.println("start server on local port 7000"); 140 | /* 141 | mainLoop((buffer, io) -> { 142 | while(io.read(buffer) != -1) { 143 | buffer.flip(); 144 | io.write(buffer); 145 | buffer.clear(); 146 | } 147 | }, 7000); 148 | */ 149 | 150 | mainLoop((buffer, io) -> { 151 | String line = io.readLine(buffer, ISO_8859_1); 152 | //System.err.println("request " + line); 153 | if (line == null) { 154 | return; 155 | } 156 | 157 | Path path = Path.of(".").resolve(line.split(" ")[1].substring(1)); 158 | //System.err.println(path); 159 | 160 | byte[] data; 161 | try { 162 | data = Files.readAllBytes(path); 163 | } catch(NoSuchFileException e) { 164 | io.write("HTTP/0.9 404 Not Found\n\n", ISO_8859_1); 165 | return; 166 | } 167 | 168 | io.write("HTTP/0.9 200 OK\nContent-Length:" + data.length +"\n\n", ISO_8859_1); 169 | io.write(ByteBuffer.wrap(data)); 170 | 171 | }, 7000); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/Task.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.lang.invoke.VarHandle; 5 | import java.time.Duration; 6 | import java.util.Objects; 7 | import java.util.concurrent.CancellationException; 8 | import java.util.concurrent.CompletionException; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.Future; 12 | import java.util.concurrent.ThreadFactory; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | import java.util.function.Supplier; 16 | 17 | public interface Task extends Future { 18 | T join() throws CancellationException; 19 | T await(Duration duration) throws CancellationException, TimeoutException; 20 | 21 | final class TaskImpl implements Task { 22 | private static final Object CANCELLED = new Object(); 23 | private static final VarHandle RESULT_HANDLE; 24 | static { 25 | try { 26 | RESULT_HANDLE = MethodHandles.lookup().findVarHandle(TaskImpl.class, "result", Object.class); 27 | } catch (NoSuchFieldException | IllegalAccessException e) { 28 | throw new AssertionError(e); 29 | } 30 | } 31 | 32 | private final static class $$$ { 33 | final E throwable; 34 | 35 | $$$(E throwable) { 36 | this.throwable = throwable; 37 | } 38 | } 39 | 40 | private final Thread virtualThread; 41 | private volatile Object result; // null -> CANCELLED or null -> value | $$$(exception) 42 | 43 | TaskImpl(ThreadFactory factory, Supplier supplier) { 44 | virtualThread = factory.newThread(() -> { 45 | Object result; 46 | try { 47 | result = supplier.get(); 48 | } catch(Error e) { // don't capture errors, only checked and unchecked exceptions 49 | throw e; 50 | } catch(Throwable e) { 51 | result = new $$$<>(e); 52 | } 53 | setResultIfNull(Objects.requireNonNull(result)); 54 | }); 55 | } 56 | 57 | private boolean setResultIfNull(Object result) { 58 | return RESULT_HANDLE.compareAndSet(this, (Object)null, result); 59 | } 60 | 61 | @Override 62 | @SuppressWarnings("unchecked") 63 | public T join() { 64 | try { 65 | virtualThread.join(); 66 | } catch (InterruptedException e) { 67 | throw new CompletionException(e); 68 | } 69 | Object result = this.result; 70 | if (result == CANCELLED) { 71 | throw new CancellationException(); 72 | } 73 | if (result instanceof $$$) { 74 | throw (($$$)result).throwable; 75 | } 76 | return (T)result; 77 | } 78 | 79 | @Override 80 | @SuppressWarnings("unchecked") 81 | public T await(Duration duration) throws TimeoutException { 82 | try { 83 | virtualThread.join(duration); 84 | } catch(InterruptedException e) { 85 | throw new CompletionException(e); 86 | } 87 | if (setResultIfNull(CANCELLED)) { 88 | throw new TimeoutException(); 89 | } 90 | Object result = this.result; 91 | if (result == CANCELLED) { 92 | throw new CancellationException(); 93 | } 94 | if (result instanceof $$$) { 95 | throw (($$$)result).throwable; 96 | } 97 | return (T)result; 98 | } 99 | 100 | @Override 101 | @SuppressWarnings("unchecked") 102 | public T get() throws CancellationException, ExecutionException, InterruptedException { 103 | virtualThread.join(); 104 | Object result = this.result; 105 | if (result == CANCELLED) { 106 | throw new CancellationException(); 107 | } 108 | if (result instanceof $$$) { 109 | throw new ExecutionException((($$$)result).throwable); 110 | } 111 | return (T)result; 112 | } 113 | 114 | @Override 115 | @SuppressWarnings("unchecked") 116 | public T get(long timeout, TimeUnit unit) throws TimeoutException, ExecutionException, InterruptedException { 117 | virtualThread.join(Duration.of(timeout, unit.toChronoUnit())); 118 | if (setResultIfNull(CANCELLED)) { 119 | throw new TimeoutException(); 120 | } 121 | Object result = this.result; 122 | if (result == CANCELLED) { 123 | throw new CancellationException(); 124 | } 125 | if (result instanceof $$$) { 126 | throw new ExecutionException((($$$)result).throwable); 127 | } 128 | return (T)result; 129 | } 130 | 131 | @Override 132 | public boolean isDone() { 133 | return result != null; 134 | } 135 | 136 | @Override 137 | public boolean cancel(boolean mayInterruptIfRunning) { 138 | return setResultIfNull(CANCELLED); 139 | } 140 | 141 | @Override 142 | public boolean isCancelled() { 143 | return result == CANCELLED; 144 | } 145 | } 146 | 147 | public static Task async(Supplier supplier) { 148 | return async0(runnable -> Thread.newThread(Thread.VIRTUAL, runnable), supplier); 149 | } 150 | 151 | public static Task async(Executor executor, Supplier supplier) { 152 | return async0(runnable -> Thread.builder().virtual(executor).task(runnable).build(), supplier); 153 | } 154 | 155 | private static Task async0(ThreadFactory factory, Supplier supplier) { 156 | var task = new TaskImpl(factory, supplier); 157 | task.virtualThread.start(); 158 | return task; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/ALotOfMessagesDemo.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import static fr.umlv.loom.actor.Actor.current; 4 | import static fr.umlv.loom.actor.Actor.exit; 5 | import static fr.umlv.loom.actor.Actor.receive; 6 | 7 | import java.util.Random; 8 | 9 | public class ALotOfMessagesDemo { 10 | private static Actor forward(Actor next) { 11 | var actor = new Actor(new Runnable() { 12 | private int counter; 13 | 14 | @Override 15 | public void run() { 16 | while (true) { 17 | receive(message -> { 18 | switch ((String) message) { 19 | case "inc" -> { 20 | if (next != null) { 21 | next.send(message); 22 | break; 23 | } 24 | current().orElseThrow().send("counter"); 25 | } 26 | case "counter" -> counter++; 27 | case "end" -> { 28 | if (next != null) { 29 | next.send(message); 30 | } else { 31 | System.out.println(counter); 32 | } 33 | exit(); 34 | } 35 | default -> throw new IllegalStateException(); 36 | } 37 | }); 38 | } 39 | } 40 | }); 41 | actor.start(); 42 | return actor; 43 | } 44 | 45 | public static void main(String[] args) { 46 | var actors = new Actor[1_000]; 47 | Actor next = null; 48 | for(var i = actors.length; --i >= 0; ) { 49 | var actor = forward(next); 50 | actors[i] = actor; 51 | next = actor; 52 | } 53 | 54 | new Random(0).ints(actors.length, 0, actors.length).forEach(i -> actors[i].send("inc")); 55 | 56 | actors[0].send("end"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/Actor.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.LinkedHashSet; 5 | import java.util.Optional; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Consumer; 8 | 9 | public final class Actor { 10 | private static final ContinuationScope SCOPE = new ContinuationScope("actor"); 11 | private static final Scheduler SCHEDULER = new Scheduler(); 12 | 13 | private static class Message { 14 | private final Object content; 15 | private final Actor sender; 16 | 17 | private Message(Object content, Actor sender) { 18 | this.content = content; 19 | this.sender = sender; 20 | } 21 | } 22 | 23 | private static class Scheduler { 24 | private final LinkedHashSet actors = new LinkedHashSet<>(); 25 | private boolean inLoop; 26 | 27 | private void register(Actor actor) { 28 | actors.add(actor); 29 | if (!inLoop && actors.size() == 1) { 30 | loop(); 31 | } 32 | } 33 | 34 | private void loop() { 35 | inLoop = true; 36 | try { 37 | while(!actors.isEmpty()) { 38 | var iterator = actors.iterator(); 39 | var actor = iterator.next(); 40 | iterator.remove(); 41 | actor.continuation.run(); 42 | } 43 | } finally { 44 | inLoop = false; 45 | } 46 | } 47 | } 48 | 49 | private class ActorContinuation extends Continuation { 50 | private ActorContinuation(Runnable runnable) { 51 | super(SCOPE, runnable); 52 | } 53 | 54 | Actor actor() { 55 | return Actor.this; 56 | } 57 | } 58 | 59 | 60 | private final ArrayDeque mailbox = new ArrayDeque<>(); 61 | private final ActorContinuation continuation; 62 | 63 | public Actor(Runnable runnable) { 64 | continuation = new ActorContinuation(() -> { 65 | try { 66 | runnable.run(); 67 | } catch(@SuppressWarnings("unused") ExitError exit) { 68 | return; 69 | } 70 | }); 71 | } 72 | 73 | public static void receive(Case... cases) { 74 | receive((content, sender) -> Case.call(content, sender, cases)); 75 | } 76 | 77 | public static void receive(Consumer consumer) { 78 | receive((content, sender) -> consumer.accept(content)); 79 | } 80 | 81 | public static void receive(BiConsumer consumer) { 82 | var actor = current().orElseThrow(() -> new IllegalStateException("no current actor available")); 83 | while(actor.mailbox.isEmpty()) { 84 | Continuation.yield(SCOPE); 85 | } 86 | Message message; 87 | while((message = actor.mailbox.poll()) != null) { 88 | consumer.accept(message.content, message.sender); 89 | } 90 | } 91 | 92 | public static Optional current() { 93 | var currentContinuation = (ActorContinuation)Continuation.getCurrentContinuation(SCOPE); 94 | return Optional.ofNullable(currentContinuation).map(ActorContinuation::actor); 95 | } 96 | 97 | public void send(Object message) { 98 | mailbox.add(new Message(message, current().orElse(null))); 99 | SCHEDULER.register(this); 100 | } 101 | 102 | public void start() { 103 | SCHEDULER.register(this); 104 | } 105 | 106 | @SuppressWarnings("serial") 107 | private static class ExitError extends Error { 108 | private static final ExitError INSTANCE = new ExitError(); 109 | private ExitError() { 110 | super(null, null, false, false); 111 | } 112 | } 113 | 114 | public static T exit() { 115 | current().orElseThrow(() -> new IllegalStateException("no current actor available")); 116 | throw ExitError.INSTANCE; 117 | } 118 | } -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/Case.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | public class Case { 7 | private final Class type; 8 | private final BiConsumer consumer; 9 | 10 | private Case(Class type, BiConsumer consumer) { 11 | this.type = type; 12 | this.consumer = consumer; 13 | } 14 | 15 | static Case Case(Class type, Consumer consumer) { 16 | return new Case(type, (content, sender) -> consumer.accept(type.cast(content))); 17 | } 18 | 19 | static Case Case(Class type, BiConsumer consumer) { 20 | return new Case(type, (content, sender) -> consumer.accept(type.cast(content), sender)); 21 | } 22 | 23 | static void call(Object content, Actor sender, Case... cases) { 24 | for(var aCase: cases) { 25 | if (aCase.type.isInstance(content)) { 26 | aCase.consumer.accept(content, sender); 27 | return; 28 | } 29 | } 30 | throw new IllegalStateException("invalid message " + content + " from " + sender); 31 | } 32 | } -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/CounterActorDemo.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import static fr.umlv.loom.actor.Actor.exit; 4 | import static fr.umlv.loom.actor.Actor.receive; 5 | import static fr.umlv.loom.actor.Case.Case; 6 | import static java.util.stream.IntStream.range; 7 | 8 | public class CounterActorDemo { 9 | static class Inc { 10 | final int amount; 11 | 12 | public Inc(int amount) { 13 | this.amount = amount; 14 | } 15 | } 16 | static class Value { /* empty */ } 17 | 18 | public static void main(String[] args) { 19 | var actor = new Actor(new Runnable() { 20 | int counter; 21 | 22 | @Override 23 | public void run() { 24 | while (true) { 25 | receive( 26 | Case(Inc.class, inc -> 27 | counter += inc.amount 28 | ), 29 | Case(Value.class, value -> { 30 | System.out.println("Value is " + counter); 31 | exit(); 32 | }) 33 | ); 34 | } 35 | } 36 | }); 37 | actor.start(); 38 | 39 | range(0, 100_000).forEach(__ -> actor.send(new Inc(1))); 40 | 41 | actor.send(new Value()); 42 | // Output: Value is 100000 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/CounterStringActorDemo.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import static fr.umlv.loom.actor.Actor.receive; 4 | import static fr.umlv.loom.actor.Actor.exit; 5 | import static java.util.stream.IntStream.range; 6 | 7 | public class CounterStringActorDemo { 8 | public static void main(String[] args) { 9 | var actor = new Actor(new Runnable() { 10 | int count; 11 | 12 | @Override 13 | public void run() { 14 | while (true) { 15 | receive(message -> { 16 | switch ((String) message) { 17 | case "increment" -> count++; 18 | case "value" -> { 19 | System.out.println("Value is " + count); 20 | exit(); 21 | } 22 | } 23 | }); 24 | } 25 | } 26 | }); 27 | actor.start(); 28 | 29 | range(0, 10_000).forEach(__ -> actor.send("increment")); 30 | 31 | actor.send("value"); 32 | // Output: Value is 10000 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/actor/CounterStringActorExprSwitchDemo.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.actor; 2 | 3 | import static fr.umlv.loom.actor.Actor.exit; 4 | import static fr.umlv.loom.actor.Actor.receive; 5 | import static java.util.stream.IntStream.range; 6 | 7 | public class CounterStringActorExprSwitchDemo { 8 | public static void main(String[] args) { 9 | var actor = new Actor(new Runnable() { 10 | int counter; 11 | 12 | @Override 13 | public void run() { 14 | while (true) { 15 | receive(message -> { 16 | switch((String)message) { 17 | case "increment" -> 18 | counter++; 19 | case "value" -> { 20 | System.out.println("Value is " + counter); 21 | exit(); 22 | } 23 | }}); 24 | } 25 | } 26 | }); 27 | actor.start(); 28 | 29 | range(0, 100_000).forEach(__ -> actor.send("increment")); 30 | 31 | actor.send("value"); 32 | // Output: Value is 100000 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/example/Example1.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | public class Example1 { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("example1"); 6 | var continuation = new Continuation(scope, () -> { 7 | System.out.println("1"); 8 | Continuation.yield(scope); 9 | System.out.println("2"); 10 | synchronized(scope) { 11 | Continuation.yield(scope); 12 | } 13 | System.out.println("3"); 14 | }); 15 | 16 | System.out.println("A"); 17 | continuation.run(); 18 | System.out.println("B"); 19 | continuation.run(); 20 | System.out.println("C"); 21 | continuation.run(); 22 | System.out.println("D"); 23 | //continuation.run(); //ISE: Continuation terminated 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/example/Example2.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | public class Example2 { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("example2"); 6 | var continuation = new Continuation(scope, () -> { 7 | var current = Continuation.getCurrentContinuation(scope); 8 | System.out.println("current " + current); 9 | }); 10 | 11 | System.out.println("continuation " + continuation); 12 | continuation.run(); 13 | 14 | System.out.println(Continuation.getCurrentContinuation(scope)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/example/Example3.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | public class Example3 { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("example3"); 6 | var data = new Object() { 7 | boolean end; 8 | }; 9 | var continuation = new Continuation(scope, () -> { 10 | for(int i = 0; i < 2; i++) { 11 | System.out.println(i); 12 | Continuation.yield(scope); 13 | } 14 | data.end = true; 15 | }); 16 | 17 | while(!data.end) { 18 | System.out.println("run"); 19 | continuation.run(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/example/Example4.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | public class Example4 { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("example4"); 6 | var continuation = new Continuation(scope, () -> { 7 | for(int i = 0; i < 2; i++) { 8 | System.out.println(i); 9 | Continuation.yield(scope); 10 | } 11 | }); 12 | 13 | while(!continuation.isDone()) { 14 | System.out.println("run"); 15 | continuation.run(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/example/Example5.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.stream.IntStream; 5 | 6 | public class Example5 { 7 | public static void main(String[] args) { 8 | var scope = new ContinuationScope("example5"); 9 | var schedulable = new ArrayDeque(); 10 | 11 | IntStream.range(0, 2).forEach(id -> { 12 | var continuation = new Continuation(scope, () -> { 13 | for(int i = 0; i < 2; i++) { 14 | System.out.println("id" + id + " " + i); 15 | 16 | schedulable.add(Continuation.getCurrentContinuation(scope)); 17 | Continuation.yield(scope); 18 | } 19 | }); 20 | schedulable.add(continuation); 21 | }); 22 | 23 | while(!schedulable.isEmpty()) { 24 | schedulable.poll().run(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/fiberactor/Actors.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.fiberactor; 2 | 3 | import java.util.concurrent.Executors; 4 | 5 | public class Actors { 6 | 7 | public static void main(String[] args) { 8 | Executors.newUnboundedVirtualThreadExecutor() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/preempt/Main.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.preempt; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.List; 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class Main { 9 | private static final ContinuationScope SCOPE = new ContinuationScope("scope"); 10 | 11 | static class Task { 12 | private final int id; 13 | private final Continuation continuation; 14 | private long sum = 0; 15 | 16 | Task(int id) { 17 | this.id = id; 18 | this.continuation = new Continuation(SCOPE, this::loop); 19 | } 20 | 21 | void loop() { 22 | for(long i = 0; i < 5_000_000_000L; i++) { 23 | sum += i; 24 | } 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "task " + id + " " + sum; 30 | } 31 | } 32 | 33 | public static void main(String[] args) { 34 | var tasks = List.of(new Task(0), new Task(1)); 35 | 36 | var queue = new ArrayDeque<>(tasks); 37 | 38 | var executor = new ScheduledThreadPoolExecutor(1); 39 | 40 | Task task; 41 | while((task = queue.poll()) != null) { 42 | var continuation = task.continuation; 43 | var id = task.id; 44 | var thread = Thread.currentThread(); 45 | var future = executor.schedule(() -> { 46 | var status = continuation.tryPreempt(thread); 47 | System.err.println("preempt " + status + " task " + id); 48 | }, 300, TimeUnit.MILLISECONDS); 49 | 50 | var startTime = System.nanoTime(); 51 | continuation.run(); 52 | var endTime = System.nanoTime(); 53 | 54 | System.err.println(task + " in " + (endTime - startTime)); 55 | 56 | future.cancel(false); 57 | if (!continuation.isDone()) { 58 | queue.offer(task); 59 | } 60 | } 61 | executor.shutdown(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/proxy/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /Host.java 3 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/proxy/TCPClassicalSocketFiberProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.util.concurrent.Executors; 9 | 10 | public class TCPClassicalSocketFiberProxy { 11 | private static Runnable runnable(Socket socket1, Socket socket2) { 12 | return () -> { 13 | var buffer = new byte[8192]; 14 | 15 | System.out.println("start " + Thread.currentThread()); 16 | try(socket1; 17 | socket2; 18 | var input1 = socket1.getInputStream(); 19 | var output2 = socket2.getOutputStream()) { 20 | for(;;) { 21 | int read = input1.read(buffer); 22 | System.out.println("read " + read + " from " + Thread.currentThread()); 23 | if (read == -1) { 24 | input1.close(); 25 | output2.close(); 26 | socket1.close(); 27 | socket2.close(); 28 | return; 29 | } 30 | 31 | output2.write(buffer, 0, read); 32 | System.out.println("write from " + Thread.currentThread()); 33 | } 34 | } catch (IOException e) { 35 | //throw new UncheckedIOException(e); 36 | } 37 | }; 38 | } 39 | 40 | @SuppressWarnings("resource") 41 | public static void main(String[] args) throws IOException { 42 | var server = new ServerSocket(); 43 | server.bind(new InetSocketAddress(7777)); 44 | System.out.println("server bound to " + server.getLocalSocketAddress()); 45 | 46 | var remote = new Socket(); 47 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); 48 | //remote.configureBlocking(false); 49 | 50 | System.out.println("accepting ..."); 51 | var client = server.accept(); 52 | //client.configureBlocking(false); 53 | 54 | var executor = Executors.newSingleThreadExecutor(Thread.ofVirtual().factory()); 55 | executor.execute(runnable(client, remote)); 56 | executor.execute(runnable(remote, client)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/proxy/TCPContinuationProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.nio.ByteBuffer; 7 | import java.nio.channels.ClosedChannelException; 8 | import java.nio.channels.SelectionKey; 9 | import java.nio.channels.Selector; 10 | import java.nio.channels.ServerSocketChannel; 11 | import java.nio.channels.SocketChannel; 12 | import java.util.ArrayDeque; 13 | /* 14 | public class TCPContinuationProxy { 15 | private static final ContinuationScope SCOPE = new ContinuationScope("scope"); 16 | 17 | private static Runnable runnable(Scheduler scheduler, SocketChannel socket1, SocketChannel socket2) { 18 | return () -> { 19 | var buffer = ByteBuffer.allocate(8192); 20 | 21 | System.out.println("start continuation " + Continuation.getCurrentContinuation(SCOPE)); 22 | try(socket1; socket2) { 23 | for(;;) { 24 | int read; 25 | while ((read = socket1.read(buffer)) == 0) { 26 | scheduler.register(socket1, SelectionKey.OP_READ); 27 | System.out.println("yield read from " + Continuation.getCurrentContinuation(SCOPE)); 28 | Continuation.yield(SCOPE); 29 | } 30 | System.out.println("read " + read + " from " +Continuation.getCurrentContinuation(SCOPE)); 31 | if (read == -1) { 32 | socket1.close(); 33 | socket2.close(); 34 | return; 35 | } 36 | buffer.flip(); 37 | 38 | do { 39 | while ((socket2.write(buffer)) == 0) { 40 | scheduler.register(socket2, SelectionKey.OP_WRITE); 41 | System.out.println("yield write from " + Continuation.getCurrentContinuation(SCOPE)); 42 | Continuation.yield(SCOPE); 43 | } 44 | 45 | System.out.println("write from " +Continuation.getCurrentContinuation(SCOPE)); 46 | 47 | } while(buffer.hasRemaining()); 48 | 49 | buffer.clear(); 50 | } 51 | } catch (@SuppressWarnings("unused") IOException e) { 52 | //throw new UncheckedIOException(e); 53 | } 54 | }; 55 | } 56 | 57 | 58 | static class Scheduler { 59 | private final Selector selector; 60 | private final ArrayDeque injected = new ArrayDeque<>(); 61 | 62 | public Scheduler(Selector selector) { 63 | this.selector = selector; 64 | } 65 | 66 | public void inject(Continuation continuation) { 67 | injected.add(continuation); 68 | } 69 | 70 | public void register(SocketChannel channel, int operation) throws ClosedChannelException { 71 | channel.register(selector, operation, Continuation.getCurrentContinuation(SCOPE)); 72 | } 73 | 74 | public void loop() throws IOException { 75 | for(;;) { 76 | while(!injected.isEmpty()) { 77 | var continuation = injected.poll(); 78 | System.out.println("injected continuation " + continuation + " run"); 79 | continuation.run(); 80 | } 81 | selector.select(key -> { 82 | var continuation = (Continuation)key.attachment(); 83 | key.interestOps(0); 84 | System.out.println("continuation " + continuation + " run"); 85 | continuation.run(); 86 | }); 87 | if (selector.keys().stream().noneMatch(SelectionKey::isValid)) { 88 | return; 89 | } 90 | } 91 | } 92 | } 93 | 94 | @SuppressWarnings("resource") 95 | public static void main(String[] args) throws IOException { 96 | var server = ServerSocketChannel.open(); 97 | server.bind(new InetSocketAddress(7777)); 98 | System.out.println("server bound to " + server.getLocalAddress()); 99 | 100 | var remote = SocketChannel.open(); 101 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); 102 | remote.configureBlocking(false); 103 | 104 | var selector = Selector.open(); 105 | var scheduler = new Scheduler(selector); 106 | 107 | System.out.println("accepting ..."); 108 | var client = server.accept(); 109 | client.configureBlocking(false); 110 | 111 | var cont1 = new Continuation(SCOPE, runnable(scheduler, client, remote)); 112 | var cont2 = new Continuation(SCOPE, runnable(scheduler, remote, client)); 113 | scheduler.inject(cont1); 114 | scheduler.inject(cont2); 115 | scheduler.loop(); 116 | } 117 | }*/ 118 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/proxy/TCPFiberProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.nio.ByteBuffer; 7 | import java.nio.channels.ServerSocketChannel; 8 | import java.nio.channels.SocketChannel; 9 | import java.util.concurrent.Executors; 10 | 11 | public class TCPFiberProxy { 12 | private static Runnable runnable(SocketChannel socket1, SocketChannel socket2) { 13 | return () -> { 14 | var buffer = ByteBuffer.allocate(8192); 15 | 16 | System.out.println("start " + Thread.currentThread()); 17 | try(socket1; socket2) { 18 | for(;;) { 19 | int read = socket1.read(buffer); 20 | System.out.println("read " + read + " from " + Thread.currentThread()); 21 | if (read == -1) { 22 | socket1.close(); 23 | socket2.close(); 24 | return; 25 | } 26 | buffer.flip(); 27 | 28 | do { 29 | socket2.write(buffer); 30 | System.out.println("write from " + Thread.currentThread()); 31 | 32 | } while(buffer.hasRemaining()); 33 | 34 | buffer.clear(); 35 | } 36 | } catch (IOException e) { 37 | //throw new UncheckedIOException(e); 38 | } 39 | }; 40 | } 41 | 42 | @SuppressWarnings("resource") 43 | public static void main(String[] args) throws IOException { 44 | var server = ServerSocketChannel.open(); 45 | server.bind(new InetSocketAddress(7777)); 46 | System.out.println("server bound to " + server.getLocalAddress()); 47 | 48 | var remote = SocketChannel.open(); 49 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); 50 | //remote.configureBlocking(false); 51 | 52 | System.out.println("accepting ..."); 53 | var client = server.accept(); 54 | //client.configureBlocking(false); 55 | 56 | var executor = Executors.newFixedThreadPool(1, Thread.ofPlatform().factory()); 57 | executor.execute(runnable(client, remote)); 58 | executor.execute(runnable(remote, client)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/proxy/TCPThreadProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.nio.ByteBuffer; 7 | import java.nio.channels.ServerSocketChannel; 8 | import java.nio.channels.SocketChannel; 9 | import java.util.concurrent.Executors; 10 | 11 | public class TCPThreadProxy { 12 | private static Runnable runnable(SocketChannel socket1, SocketChannel socket2) { 13 | return () -> { 14 | var buffer = ByteBuffer.allocate(8192); 15 | 16 | System.out.println("start " + Thread.currentThread()); 17 | try(socket1; socket2) { 18 | for(;;) { 19 | int read = socket1.read(buffer); 20 | System.out.println("read " + read + " from " + Thread.currentThread()); 21 | if (read == -1) { 22 | socket1.close(); 23 | socket2.close(); 24 | return; 25 | } 26 | buffer.flip(); 27 | 28 | do { 29 | socket2.write(buffer); 30 | System.out.println("write from " + Thread.currentThread()); 31 | 32 | } while(buffer.hasRemaining()); 33 | 34 | buffer.clear(); 35 | } 36 | } catch (@SuppressWarnings("unused") IOException e) { 37 | //throw new UncheckedIOException(e); 38 | } 39 | }; 40 | } 41 | 42 | @SuppressWarnings("resource") 43 | public static void main(String[] args) throws IOException { 44 | var server = ServerSocketChannel.open(); 45 | server.bind(new InetSocketAddress(7777)); 46 | System.out.println("server bound to " + server.getLocalAddress()); 47 | 48 | var remote = SocketChannel.open(); 49 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), 7)); 50 | //remote.configureBlocking(false); 51 | 52 | System.out.println("accepting ..."); 53 | var client = server.accept(); 54 | //client.configureBlocking(false); 55 | 56 | var executor = Executors.newFixedThreadPool(2, Thread.ofPlatform().factory()); 57 | executor.execute(runnable(client, remote)); 58 | executor.execute(runnable(remote, client)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/BarrierExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Objects; 5 | import java.util.stream.IntStream; 6 | 7 | public class BarrierExample { 8 | public static class Barrier { 9 | private final int party; 10 | private final Scheduler scheduler; 11 | private final ArrayList waitQueue; 12 | 13 | public Barrier(int party, Scheduler scheduler) { 14 | if (party <= 0) { 15 | throw new IllegalArgumentException(); 16 | } 17 | this.party = party; 18 | this.scheduler = Objects.requireNonNull(scheduler); 19 | this.waitQueue = new ArrayList<>(party); 20 | } 21 | 22 | public void await() { 23 | var continuation = Scheduler.currentContinuation(); 24 | waitQueue.add(continuation); 25 | if (waitQueue.size() == party) { 26 | waitQueue.forEach(scheduler::register); 27 | waitQueue.clear(); 28 | } 29 | scheduler.yield(); 30 | } 31 | } 32 | 33 | public static void main(String[] args) { 34 | var scheduler = new FifoScheduler(); 35 | var barrier = new Barrier(5, scheduler); 36 | IntStream.range(0, 5).forEach(id -> { 37 | scheduler.schedule(() -> { 38 | System.out.println("wait " + id); 39 | barrier.await(); 40 | System.out.println("released " + id); 41 | }); 42 | }); 43 | scheduler.loop(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/ExchangerExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.Objects; 4 | 5 | public class ExchangerExample { 6 | public static class Exchanger { 7 | private final Scheduler scheduler; 8 | private V value; 9 | private Continuation continuation; 10 | 11 | public Exchanger(Scheduler scheduler) { 12 | this.scheduler = Objects.requireNonNull(scheduler); 13 | } 14 | 15 | public V exchange(V value) { 16 | Objects.requireNonNull(value); 17 | var continuation = Scheduler.currentContinuation(); 18 | if (this.value == null) { 19 | this.value = value; 20 | this.continuation = continuation; 21 | scheduler.yield(); 22 | var result = this.value; 23 | this.value = null; 24 | return result; 25 | } 26 | var result = this.value; 27 | this.value = value; 28 | scheduler.register(this.continuation); 29 | this.continuation = null; 30 | return result; 31 | } 32 | } 33 | 34 | public static void main(String[] args) { 35 | var scheduler = new FifoScheduler(); 36 | var exchanger = new Exchanger(scheduler); 37 | scheduler.schedule(() -> { 38 | System.out.println("cont1: " + exchanger.exchange("hello")); 39 | }); 40 | scheduler.schedule(() -> { 41 | System.out.println("cont2: " + exchanger.exchange("hi")); 42 | }); 43 | scheduler.loop(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/FairScheduler.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.IdentityHashMap; 4 | import java.util.TreeMap; 5 | 6 | public class FairScheduler implements Scheduler { 7 | private final TreeMap schedulable = new TreeMap<>(); 8 | private final IdentityHashMap pendingMap = new IdentityHashMap<>(); 9 | private long startTime; 10 | 11 | private void put(long executionTime, Continuation continuation) { 12 | var oldContinuation = schedulable.put(executionTime, continuation); 13 | if (oldContinuation == null) { 14 | return; 15 | } 16 | long time = executionTime; 17 | while (schedulable.containsKey(time)) { // avoid collision 18 | time++; 19 | } 20 | schedulable.put(time, oldContinuation); 21 | } 22 | 23 | @Override 24 | public void register(Continuation continuation) { 25 | long executionTime = pendingMap.getOrDefault(continuation, 0L); 26 | if (executionTime >= 0) { 27 | put(executionTime, continuation); 28 | pendingMap.remove(continuation); 29 | } 30 | } 31 | 32 | @Override 33 | public void yield() { 34 | long executionTime = System.nanoTime() - startTime; 35 | var continuation = Scheduler.currentContinuation(); 36 | if (pendingMap.get(continuation) == -1L) { 37 | put(executionTime, continuation); 38 | pendingMap.remove(continuation); 39 | } 40 | Scheduler.super.yield(); 41 | } 42 | 43 | @Override 44 | public void loop() { 45 | while(!schedulable.isEmpty()) { 46 | var continuation = schedulable.pollFirstEntry().getValue(); 47 | startTime = System.nanoTime(); 48 | pendingMap.put(continuation, -1L); 49 | continuation.run(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/FifoScheduler.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.ArrayDeque; 4 | 5 | public class FifoScheduler implements Scheduler { 6 | private final ArrayDeque schedulable = new ArrayDeque<>(); 7 | 8 | @Override 9 | public void register(Continuation continuation) { 10 | schedulable.add(continuation); 11 | } 12 | 13 | @Override 14 | public void loop() { 15 | while(!schedulable.isEmpty()) { 16 | var continuation = schedulable.poll(); 17 | continuation.run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/InterruptibleScheduler.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | public class InterruptibleScheduler { 4 | public static void main(String[] args) { 5 | var scope = new ContinuationScope("scope"); 6 | var continuation = new Continuation(scope, () -> { 7 | for(;;) { 8 | // do nothing 9 | } 10 | }); 11 | 12 | var current = Thread.currentThread(); 13 | 14 | var thread = new Thread(() -> { 15 | try { 16 | Thread.sleep(1_000); 17 | } catch (InterruptedException e) { 18 | throw new AssertionError(e); 19 | } 20 | System.out.println("preempt"); 21 | var status = continuation.tryPreempt(current); 22 | System.out.println("preempt status " + status); 23 | }); 24 | thread.start(); 25 | 26 | continuation.run(); 27 | 28 | System.out.println("it works !"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/LockExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Objects; 5 | import java.util.stream.IntStream; 6 | 7 | public class LockExample { 8 | public static class Lock { 9 | private final Scheduler scheduler; 10 | private final ArrayList waitQueue = new ArrayList<>(); 11 | private int depth; 12 | private Continuation owner; 13 | 14 | public Lock(Scheduler scheduler) { 15 | this.scheduler = Objects.requireNonNull(scheduler); 16 | } 17 | 18 | public void lock() { 19 | Continuation continuation = Scheduler.currentContinuation(); 20 | for (;;) { 21 | if (depth == 0) { 22 | depth = 1; 23 | owner = continuation; 24 | break; 25 | } 26 | if (owner == continuation) { 27 | depth++; 28 | break; 29 | } 30 | waitQueue.add(continuation); 31 | scheduler.yield(); 32 | } 33 | } 34 | 35 | public void unlock() { 36 | Continuation continuation = Scheduler.currentContinuation(); 37 | if (owner == continuation) { 38 | if (depth == 1) { 39 | depth = 0; 40 | owner = null; 41 | waitQueue.forEach(scheduler::register); 42 | waitQueue.clear(); 43 | } else { 44 | depth--; 45 | } 46 | } else { 47 | throw new IllegalStateException("not locked !"); 48 | } 49 | } 50 | } 51 | 52 | public static void main(String[] args) { 53 | var scheduler = new RandomScheduler(); 54 | var lock = new Lock(scheduler); 55 | var shared = new Object() { 56 | int x; 57 | int y; 58 | }; 59 | 60 | IntStream.range(0, 2).forEach(id -> { 61 | scheduler.schedule(() -> { 62 | for (;;) { 63 | lock.lock(); 64 | try { 65 | shared.x = id; 66 | scheduler.pause(); 67 | shared.y = id; 68 | } finally { 69 | lock.unlock(); 70 | } 71 | scheduler.pause(); 72 | } 73 | }); 74 | }); 75 | scheduler.schedule(() -> { 76 | for (;;) { 77 | lock.lock(); 78 | try { 79 | System.out.println(shared.x + " " + shared.y); 80 | } finally { 81 | lock.unlock(); 82 | } 83 | scheduler.pause(); 84 | } 85 | }); 86 | 87 | scheduler.loop(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/RandomScheduler.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | public class RandomScheduler implements Scheduler { 7 | private final ArrayList schedulable = new ArrayList<>(); 8 | 9 | @Override 10 | public void register(Continuation continuation) { 11 | schedulable.add(continuation); 12 | } 13 | 14 | @Override 15 | public void loop() { 16 | var random = ThreadLocalRandom.current(); 17 | while(!schedulable.isEmpty()) { 18 | var continuation = schedulable.remove(random.nextInt(schedulable.size())); 19 | continuation.run(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/Scheduler.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | public interface Scheduler { 4 | void register(Continuation continuation); 5 | void loop(); 6 | 7 | default void schedule(Runnable runnable) { 8 | var continuation = new Continuation(SchedulerImpl.SCOPE, runnable); 9 | register(continuation); 10 | } 11 | 12 | default void yield() { 13 | currentContinuation(); // verify there is a current continuation 14 | Continuation.yield(SchedulerImpl.SCOPE); 15 | } 16 | 17 | default void pause() { 18 | register(currentContinuation()); 19 | Continuation.yield(SchedulerImpl.SCOPE); 20 | } 21 | 22 | static Continuation currentContinuation() { 23 | var continuation = Continuation.getCurrentContinuation(SchedulerImpl.SCOPE); 24 | if (continuation == null) { 25 | throw new IllegalStateException("no current continuation"); 26 | } 27 | return continuation; 28 | } 29 | static boolean hasCurrentContinuation() { 30 | return Continuation.getCurrentContinuation(SchedulerImpl.SCOPE) != null; 31 | } 32 | } -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/SchedulerImpl.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | class SchedulerImpl { 4 | static final ContinuationScope SCOPE = new ContinuationScope("Scheduler"); 5 | } 6 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/SemaphoreExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Objects; 5 | import java.util.stream.IntStream; 6 | 7 | public class SemaphoreExample { 8 | public static class Semaphore { 9 | private int permits; 10 | private final Scheduler scheduler; 11 | private final ArrayList waitQueue; 12 | 13 | public Semaphore(int permits, Scheduler scheduler) { 14 | if (permits <= 0) { 15 | throw new IllegalArgumentException(); 16 | } 17 | this.permits = permits; 18 | this.scheduler = Objects.requireNonNull(scheduler); 19 | this.waitQueue = new ArrayList<>(); 20 | } 21 | 22 | public void acquire(int somePermits) { 23 | for(;;) { 24 | if (permits >= somePermits) { 25 | permits -= somePermits; 26 | return; 27 | } 28 | var continuation = Scheduler.currentContinuation(); 29 | waitQueue.add(continuation); 30 | scheduler.yield(); 31 | } 32 | } 33 | 34 | public void release(int somePermits) { 35 | permits += somePermits; 36 | waitQueue.forEach(scheduler::register); 37 | waitQueue.clear(); 38 | } 39 | } 40 | 41 | public static void main(String[] args) { 42 | //var scheduler = new FifoScheduler(); 43 | var scheduler = new RandomScheduler(); 44 | var semaphore = new Semaphore(5, scheduler); 45 | IntStream.rangeClosed(1, 5).forEach(id -> { 46 | scheduler.schedule(() -> { 47 | for(;;) { 48 | System.out.println("try acquire " + id); 49 | semaphore.acquire(id); 50 | System.out.println("acquired " + id); 51 | 52 | scheduler.pause(); 53 | 54 | System.out.println("try release " + id); 55 | semaphore.release(id); 56 | System.out.println("released " + id); 57 | 58 | scheduler.pause(); 59 | } 60 | }); 61 | }); 62 | scheduler.loop(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/scheduler/WorkQueueExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import static java.util.stream.IntStream.range; 4 | 5 | import java.util.ArrayDeque; 6 | import java.util.Objects; 7 | 8 | public class WorkQueueExample { 9 | public static class Condition { 10 | private final Scheduler scheduler; 11 | private final ArrayDeque waitQueue = new ArrayDeque<>(); 12 | 13 | public Condition(Scheduler scheduler) { 14 | this.scheduler = scheduler; 15 | } 16 | 17 | public void await() { 18 | var currentContinuation = Scheduler.currentContinuation(); 19 | waitQueue.offer(currentContinuation); 20 | scheduler.yield(); 21 | } 22 | 23 | public void signal() { 24 | Scheduler.currentContinuation(); // check that this a thread has a continuation 25 | var continuation = waitQueue.poll(); 26 | if (continuation == null) { 27 | return; 28 | } 29 | scheduler.register(continuation); 30 | } 31 | 32 | public void signalAll() { 33 | Scheduler.currentContinuation(); // check that this a thread has a continuation 34 | waitQueue.forEach(scheduler::register); 35 | waitQueue.clear(); 36 | } 37 | } 38 | 39 | public static class WorkQueue { 40 | private final int capacity; 41 | private final ArrayDeque queue; 42 | private final Condition isEmpty; 43 | private final Condition isFull; 44 | 45 | public WorkQueue(int capacity, Scheduler scheduler) { 46 | Objects.requireNonNull(scheduler); 47 | this.capacity = capacity; 48 | this.queue = new ArrayDeque<>(capacity); 49 | this.isEmpty = new Condition(scheduler); 50 | this.isFull = new Condition(scheduler); 51 | } 52 | 53 | public T take() { 54 | while (queue.isEmpty()) { 55 | isEmpty.await(); 56 | } 57 | isFull.signalAll(); 58 | return queue.pop(); 59 | } 60 | 61 | public void put(T element) { 62 | while (queue.size() == capacity) { 63 | isFull.await(); 64 | } 65 | isEmpty.signalAll(); 66 | queue.offer(element); 67 | } 68 | } 69 | 70 | public static void main(String[] args) { 71 | var scheduler = new RandomScheduler(); 72 | var workQueue = new WorkQueue(4, scheduler); 73 | 74 | range(0, 10).forEach(id -> { 75 | scheduler.schedule(() -> { 76 | for(;;) { 77 | System.out.println(id + ": produce " + id); 78 | workQueue.put(id); 79 | scheduler.pause(); 80 | } 81 | }); 82 | }); 83 | range(0, 10).forEach(id -> { 84 | scheduler.schedule(() -> { 85 | for(;;) { 86 | System.out.println(id + ": consume " + workQueue.take()); 87 | scheduler.pause(); 88 | } 89 | }); 90 | }); 91 | 92 | scheduler.loop(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/test/JayTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | import java.util.function.Function; 7 | import java.util.function.Predicate; 8 | import java.util.function.Supplier; 9 | import java.util.stream.Stream; 10 | 11 | /* 12 | public class JayTest { 13 | private static class TesterContinuation extends Continuation { 14 | private static final ContinuationScope SCOPE = new ContinuationScope("JayTest"); 15 | 16 | private final ArrayList errors = new ArrayList<>(); 17 | 18 | private TesterContinuation(Runnable target) { 19 | super(SCOPE, target); 20 | } 21 | 22 | static TesterContinuation current() { 23 | return (TesterContinuation) Continuation.getCurrentContinuation(SCOPE); 24 | } 25 | } 26 | 27 | public static class TestError extends AssertionError { 28 | private static final long serialVersionUID = 1; 29 | private static final Set HIDDEN_CLASS_NAMES = 30 | Set.of("fr.umlv.loom.test.JayTest", "fr.umlv.loom.test.JayTest$Query", "fr.umlv.loom.test.JayTest$TesterContinuation", "java.lang.Continuation"); 31 | 32 | public TestError(String message) { 33 | super(message); 34 | } 35 | 36 | public TestError(String message, Throwable cause) { 37 | super(message, cause); 38 | } 39 | 40 | @Override 41 | public synchronized Throwable fillInStackTrace(ContinuationScope scope) { 42 | var stackWalker = StackWalker.getInstance(scope); 43 | setStackTrace(stackWalker.walk(frames -> frames.skip(7).flatMap(frame -> { 44 | if (HIDDEN_CLASS_NAMES.contains(frame.getClassName())) { 45 | return Stream.empty(); 46 | } 47 | return Stream.of(frame.toStackTraceElement()); 48 | }).toArray(StackTraceElement[]::new))); 49 | return this; 50 | } 51 | } 52 | 53 | @FunctionalInterface 54 | public interface Executable { 55 | void execute() throws Throwable; 56 | } 57 | 58 | public static void test(String description, Executable executable) { 59 | Objects.requireNonNull(description); 60 | Objects.requireNonNull(executable); 61 | 62 | var continuation = new TesterContinuation(() -> { 63 | try { 64 | executable.execute(); 65 | } catch(Throwable e) { 66 | TesterContinuation.current().errors.add(new TestError("unexpected exception", e)); 67 | } 68 | }); 69 | 70 | continuation.run(); 71 | 72 | if (continuation.errors.isEmpty()) { 73 | return; 74 | } 75 | var error = new TestError(description); 76 | continuation.errors.forEach(error::addSuppressed); 77 | 78 | var current = TesterContinuation.current(); 79 | if (current != null) { 80 | current.errors.add(error); 81 | } else { 82 | throw error; 83 | } 84 | } 85 | 86 | private static void checkInsideTest() { 87 | if (TesterContinuation.current() == null) { 88 | throw new IllegalStateException("not enclosed in test()"); 89 | } 90 | } 91 | 92 | private static Query expect0(T value) { 93 | checkInsideTest(); 94 | return predicate -> new Result(predicate.test(value), "value " + value); 95 | } 96 | public static Query expect(T value) { 97 | return expect0(value); 98 | } 99 | public static Query> expect(Supplier supplier) { 100 | return expect0((Supplier) supplier::get); 101 | } 102 | 103 | public record Result(boolean valid, String text) { 104 | public Result { 105 | Objects.requireNonNull(text); 106 | } 107 | 108 | public Result with(Function mapper) { 109 | return new Result(valid, mapper.apply(text)); 110 | } 111 | } 112 | 113 | @FunctionalInterface 114 | public interface Query { 115 | Result eval(Predicate predicate); 116 | 117 | default Query not() { 118 | return predicate -> eval(Predicate.not(predicate)).with(text -> "not " + text); 119 | } 120 | 121 | default void to(String message, Predicate predicate) { 122 | Objects.requireNonNull(message); 123 | Objects.requireNonNull(predicate); 124 | var result = eval(predicate); 125 | if (!result.valid) { 126 | TesterContinuation.current().errors.add(new TestError(result.text + " " + message)); 127 | } 128 | } 129 | 130 | default void toBe(Object expected) { 131 | to("is equals to " + expected, value -> Objects.equals(value, expected)); 132 | } 133 | 134 | default Query returnValue() { 135 | return predicate -> { 136 | return eval (value -> { 137 | if (value instanceof Supplier supplier) { 138 | return predicate.test(supplier.get()); 139 | } 140 | return false; 141 | }).with(text -> "return " + text); 142 | }; 143 | } 144 | } 145 | } 146 | */ -------------------------------------------------------------------------------- /old/main/java/fr/umlv/loom/test/Main.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.test; 2 | import static fr.umlv.loom.test.JayTest.*; 3 | 4 | // ~/jdk/jdk-15-loom/bin/java --enable-preview --module-path target/main/artifact --module fr.umlv.loom/fr.umlv.loom.test.Main 5 | /*public class Main { 6 | public static void main(String[] args) { 7 | // typesafe API loosely based on JavaScript library Jest 8 | 9 | test("all", () -> { 10 | 11 | test("two plus two is four", () -> { 12 | //expect(2 + 2).toBe(4); 13 | expect(2 + 2).toBe(3); 14 | }); 15 | 16 | test("two plus two is not three", () -> { 17 | //expect(2 + 2).not().toBe(3); 18 | expect(2 + 2).not().toBe(4); 19 | }); 20 | 21 | test("return value is correct", () -> { 22 | expect(() -> "hell").returnValue().toBe("hello"); 23 | expect(() -> "ban").returnValue().toBe("banzai"); 24 | }); 25 | 26 | }); 27 | } 28 | }*/ 29 | -------------------------------------------------------------------------------- /old/test/java/fr/umlv/loom/ContextTests.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static java.util.stream.IntStream.range; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | @SuppressWarnings("static-method") 10 | public class ContextTests { 11 | @Test 12 | public void defaultValue() { 13 | var context = new Context<>(() -> 42); 14 | context.enter(() -> { 15 | assertEquals(42, (int)context.getValue()); 16 | }); 17 | } 18 | 19 | @Test 20 | public void enclosed() { 21 | var context = new Context<>(() -> 1); 22 | var context2 = new Context<>(() -> 2); 23 | 24 | context.enter(() -> { 25 | context2.enter(() -> { 26 | assertEquals(1, (int)context.getValue()); 27 | assertEquals(2, (int)context2.getValue()); 28 | }); 29 | }); 30 | } 31 | 32 | @Test 33 | public void multipleThreads() throws InterruptedException { 34 | var context = new Context(() -> null); 35 | 36 | var threads = range(0, 4).mapToObj(i -> { 37 | return new Thread(() -> { 38 | context.enter(() -> { 39 | context.setValue(Thread.currentThread()); 40 | assertEquals(Thread.currentThread(), context.getValue()); 41 | }); 42 | }); 43 | }).collect(toList()); 44 | 45 | threads.forEach(Thread::start); 46 | 47 | for(var thread: threads) { 48 | thread.join(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /old/test/java/fr/umlv/loom/EventContinuationTests.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | @SuppressWarnings("static-method") 8 | public class EventContinuationTests { 9 | enum Command { LEFT, RIGHT, QUIT } 10 | enum Direction { NORTH, EAST, SOUTH, WEST } 11 | 12 | private static final Direction[] DIRECTIONS = Direction.values(); 13 | 14 | private static Direction turn(Direction direction, int shift) { 15 | return DIRECTIONS[(direction.ordinal() + shift + DIRECTIONS.length) % DIRECTIONS.length]; 16 | } 17 | 18 | @Test 19 | public void robot() { 20 | EventContinuation robot = new EventContinuation<>((yielder, parameter) -> { 21 | var direction = Direction.NORTH; 22 | var command = parameter; 23 | for(;;) { 24 | direction = switch(command) { 25 | case LEFT -> turn(direction, -1); 26 | case RIGHT -> turn(direction, 1); 27 | case QUIT -> direction; 28 | }; 29 | command = yielder.yield(direction); 30 | } 31 | }); 32 | 33 | assertEquals(Direction.EAST, robot.execute(Command.RIGHT)); 34 | assertEquals(Direction.SOUTH, robot.execute(Command.RIGHT)); 35 | assertEquals(Direction.EAST, robot.execute(Command.LEFT)); 36 | assertEquals(Direction.EAST, robot.execute(Command.QUIT)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /old/test/java/fr/umlv/loom/FileDirs.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static fr.umlv.loom.Task.async; 4 | import static java.nio.file.Files.lines; 5 | import static java.nio.file.Files.walk; 6 | import static java.util.function.Predicate.not; 7 | import static java.util.stream.Collectors.toList; 8 | 9 | import java.io.IOException; 10 | import java.io.UncheckedIOException; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.function.Predicate; 14 | import org.junit.jupiter.api.Test; 15 | 16 | @SuppressWarnings("static-method") 17 | public class FileDirs { 18 | private static long lineCountFile(Path path) { 19 | try(var lines = lines(path)) { 20 | return lines.count(); 21 | } catch(IOException e) { 22 | throw new UncheckedIOException(e); 23 | } 24 | } 25 | 26 | private static long lineCount(Path directory, Predicate filter, int limit) throws IOException { 27 | try(var files = walk(directory)) { 28 | return files 29 | .filter(not(Files::isDirectory)) 30 | .filter(filter) 31 | .limit(limit) 32 | .map(path -> async(() -> lineCountFile(path))) 33 | .collect(toList()) 34 | .stream() 35 | .mapToLong(Task::join) 36 | .sum(); 37 | } catch(UncheckedIOException e) { 38 | throw e.getCause(); 39 | } 40 | } 41 | 42 | @Test 43 | public void testLineCount() throws IOException { 44 | var lineCount = lineCount(Path.of("."), path -> path.toString().endsWith(".java"), 5); 45 | System.out.println("lineCount " + lineCount); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /old/test/java/fr/umlv/loom/GeneratorsTests.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static java.util.stream.IntStream.range; 5 | import static org.junit.jupiter.api.Assertions.assertAll; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import org.junit.jupiter.api.Disabled; 13 | import org.junit.jupiter.api.Test; 14 | 15 | @SuppressWarnings("static-method") 16 | @Disabled 17 | public class GeneratorsTests { 18 | @Test 19 | public void iteratorSimple() { 20 | assertAll( 21 | () -> assertFalse(Generators.iterator(consumer -> { /* */}).hasNext()), 22 | () -> assertTrue(Generators.iterator(consumer -> { consumer.accept("foo");}).hasNext()), 23 | () -> assertEquals("bar", Generators.iterator(consumer -> { consumer.accept("bar");}).next()), 24 | () -> { 25 | var it = Generators.iterator(consumer -> { consumer.accept("booz"); consumer.accept("baz");}); 26 | assertTrue(it.hasNext()); 27 | assertEquals("booz", it.next()); 28 | assertTrue(it.hasNext()); 29 | assertEquals("baz", it.next()); 30 | assertFalse(it.hasNext()); 31 | } 32 | ); 33 | } 34 | 35 | @Test 36 | public void iteratorWithALotOfObject() { 37 | var it = Generators.iterator(consumer -> { range(0, 1_000).forEach(consumer::accept); }); 38 | var list = new ArrayList(); 39 | it.forEachRemaining(list::add); 40 | assertEquals(range(0, 1_000).boxed().collect(toList()), list); 41 | } 42 | 43 | @Test 44 | public void streamSimple() { 45 | assertAll( 46 | () -> assertFalse(Generators.stream(consumer -> { /* */}).findFirst().isPresent()), 47 | () -> assertTrue(Generators.stream(consumer -> { consumer.accept("foo");}).findFirst().isPresent()), 48 | () -> assertEquals("bar", Generators.stream(consumer -> { consumer.accept("bar");}).findFirst().orElseThrow()), 49 | () -> { 50 | var stream = Generators.stream(consumer -> { consumer.accept("booz"); consumer.accept("baz");}); 51 | assertEquals(List.of("booz", "baz"), stream.collect(toList())); 52 | } 53 | ); 54 | } 55 | 56 | @Test 57 | public void streamWithALotOfObject() { 58 | var stream = Generators.stream(consumer -> { range(0, 1_000).forEach(consumer::accept); }); 59 | assertEquals(range(0, 1_000).boxed().collect(toList()), stream.collect(toList())); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /old/test/java/fr/umlv/loom/TaskTests.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom; 2 | 3 | import static fr.umlv.loom.Task.async; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.Assertions.assertFalse; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTimeout; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | import java.time.Duration; 11 | import java.util.concurrent.CancellationException; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | import org.junit.jupiter.api.Test; 16 | 17 | @SuppressWarnings("static-method") 18 | public class TaskTests { 19 | @Test 20 | public void taskAsyncAwait() { 21 | var task = async(() -> 2); 22 | assertEquals(2, (int)task.join()); 23 | assertFalse(task.isCancelled()); 24 | assertTrue(task.isDone()); 25 | } 26 | 27 | @Test 28 | public void taskAsyncAwaitParallel() { 29 | assertTimeout(Duration.ofMillis(250), () -> { 30 | var task = async(() -> sleep(200)); 31 | var task2 = async(() -> sleep(200)); 32 | task.join(); 33 | task2.join(); 34 | }); 35 | } 36 | 37 | @Test 38 | public void taskException() { 39 | class FooException extends RuntimeException { 40 | private static final long serialVersionUID = 1L; 41 | } 42 | var task = async(() -> { throw new FooException(); }); 43 | assertThrows(FooException.class, () -> task.join()); 44 | assertFalse(task.isCancelled()); 45 | assertTrue(task.isDone()); 46 | assertThrows(FooException.class, () -> task.join()); 47 | assertThrows(FooException.class, () -> task.await(Duration.ofNanos(0))); 48 | assertThrows(ExecutionException.class, () -> task.get()); 49 | assertThrows(ExecutionException.class, () -> task.get(0, TimeUnit.MILLISECONDS)); 50 | } 51 | 52 | @Test 53 | public void taskCancelled() { 54 | var task = async(() -> sleep(100)); 55 | assertTrue(task.cancel(false)); 56 | assertTrue(task.isCancelled()); 57 | assertTrue(task.isDone()); 58 | assertThrows(CancellationException.class, () -> task.join()); 59 | assertThrows(CancellationException.class, () -> task.join()); 60 | } 61 | 62 | @Test 63 | public void taskTimeout() { 64 | var task = async(() -> sleep(100)); 65 | assertThrows(TimeoutException.class, () -> task.get(10, TimeUnit.MILLISECONDS)); 66 | assertTrue(task.isCancelled()); 67 | assertTrue(task.isDone()); 68 | assertThrows(CancellationException.class, () -> task.get(10, TimeUnit.MILLISECONDS)); 69 | assertThrows(CancellationException.class, () -> task.join()); 70 | assertThrows(CancellationException.class, () -> task.get()); 71 | } 72 | 73 | private static long sleep(int millis) { 74 | var start = System.nanoTime(); 75 | try { 76 | Thread.sleep(millis); 77 | } catch (InterruptedException e) { 78 | throw new AssertionError(e); 79 | } 80 | var end = System.nanoTime(); 81 | return end - start; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | fr.umlv.loom 8 | loom 9 | jar 10 | 1.0-SNAPSHOT 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | org.junit.jupiter 19 | junit-jupiter-api 20 | 5.9.3 21 | test 22 | 23 | 24 | org.junit.jupiter 25 | junit-jupiter-engine 26 | 5.9.3 27 | test 28 | 29 | 30 | com.fasterxml.jackson.core 31 | jackson-databind 32 | 2.15.0 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-compiler-plugin 41 | 3.11.0 42 | 43 | 21 44 | 21 45 | 46 | --enable-preview 47 | --add-exports 48 | java.base/jdk.internal.vm=ALL-UNNAMED 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-surefire-plugin 55 | 3.1.2 56 | 57 | --enable-preview 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /scoop about scopes.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/loom-fiber/317ce459a2152467625eb9dc2db2120136d6cd3a/scoop about scopes.odp -------------------------------------------------------------------------------- /scoop about scopes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/loom-fiber/317ce459a2152467625eb9dc2db2120136d6cd3a/scoop about scopes.pdf -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/continuation/Continuation.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import fr.umlv.loom.executor.UnsafeExecutors; 4 | 5 | import java.util.concurrent.locks.Condition; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | 8 | public class Continuation { 9 | private enum State { NEW, RUNNING, WAITED, TERMINATED } 10 | 11 | private static final ScopedValue CONTINUATION_SCOPE_LOCAL = ScopedValue.newInstance(); 12 | 13 | private final Runnable runnable; 14 | private final Thread owner; 15 | private State state = State.NEW; 16 | private final ReentrantLock lock = new ReentrantLock(); 17 | private final Condition condition = lock.newCondition(); 18 | 19 | public Continuation(Runnable runnable) { 20 | this.runnable = runnable; 21 | this.owner = Thread.currentThread(); 22 | } 23 | 24 | public void run() { 25 | if (Thread.currentThread() != owner) { 26 | throw new IllegalStateException(); 27 | } 28 | switch (state) { 29 | case NEW -> { 30 | state = State.RUNNING; 31 | var executor = UnsafeExecutors.virtualThreadExecutor(Runnable::run); 32 | executor.execute(() -> { 33 | ScopedValue.runWhere(CONTINUATION_SCOPE_LOCAL, this, runnable); 34 | state = State.TERMINATED; 35 | }); 36 | } 37 | case WAITED -> { 38 | state = State.RUNNING; 39 | lock.lock(); 40 | try { 41 | condition.signal(); 42 | } finally { 43 | lock.unlock(); 44 | } 45 | } 46 | case RUNNING, TERMINATED -> throw new IllegalStateException(); 47 | } 48 | } 49 | 50 | public static void yield() { 51 | if (!CONTINUATION_SCOPE_LOCAL.isBound()) { 52 | throw new IllegalStateException(); 53 | } 54 | var continuation = CONTINUATION_SCOPE_LOCAL.get(); 55 | continuation.lock.lock(); 56 | try { 57 | continuation.state = State.WAITED; 58 | continuation.condition.await(); 59 | } catch (InterruptedException e) { 60 | Thread.currentThread().interrupt(); 61 | } finally { 62 | continuation.lock.unlock(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/continuation/ContinuationMain.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | public class ContinuationMain { 4 | public static void main(String[] args) { 5 | var continuation = new Continuation(() -> { 6 | System.out.println("C1"); 7 | Continuation.yield(); 8 | System.out.println("C2"); 9 | Continuation.yield(); 10 | System.out.println("C3"); 11 | }); 12 | 13 | System.out.println("start"); 14 | continuation.run(); 15 | System.out.println("came back"); 16 | continuation.run(); 17 | System.out.println("back again"); 18 | continuation.run(); 19 | System.out.println("back again again"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_10_future_state_and_shutdown.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.concurrent.StructuredTaskScope; 4 | 5 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._10_future_state_and_shutdown 6 | public interface _10_future_state_and_shutdown { 7 | static void main(String[] args) throws InterruptedException { 8 | try (var scope = new StructuredTaskScope()) { 9 | var task = scope.fork(() -> { 10 | Thread.sleep(1_000); 11 | return 42; 12 | }); 13 | System.out.println(task.state()); // UNAVAILABLE 14 | scope.join(); 15 | System.out.println(task.state()); // SUCCESS 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_11_shutdown_on_success.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.StructuredTaskScope; 5 | 6 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._11_shutdown_on_success 7 | public interface _11_shutdown_on_success { 8 | static void main(String[] args) throws InterruptedException, ExecutionException { 9 | try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) { 10 | scope.fork(() -> { 11 | Thread.sleep(1_000); 12 | return 1; 13 | }); 14 | scope.fork(() -> { 15 | Thread.sleep(42); 16 | return 2; 17 | }); 18 | var result = scope.join().result(); 19 | System.out.println(result); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_12_shutdown_on_failure.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.StructuredTaskScope; 6 | 7 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._12_shutdown_on_failure 8 | public interface _12_shutdown_on_failure { 9 | static void main(String[] args) throws InterruptedException, ExecutionException { 10 | try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { 11 | var task1 = scope.fork(() -> { 12 | Thread.sleep(1_000); 13 | return 1; 14 | }); 15 | var task2 = scope.fork(() -> { 16 | Thread.sleep(42); 17 | return "2"; 18 | }); 19 | scope.join().throwIfFailed(); 20 | System.out.println(task1.get() + task2.get()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_13_timeout.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.concurrent.ExecutionException; 6 | import java.util.concurrent.StructuredTaskScope; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._13_timeout 10 | public interface _13_timeout { 11 | static void main(String[] args) throws InterruptedException, ExecutionException { 12 | try (var scope = new StructuredTaskScope<>()) { 13 | var task1 = scope.fork(() -> { 14 | Thread.sleep(1_000); // throws InterruptedException 15 | return 1; 16 | }); 17 | var task2 = scope.fork(() -> { 18 | Thread.sleep(5_000); // throws InterruptedException 19 | return 2; 20 | }); 21 | try { 22 | scope.joinUntil(Instant.now().plus(Duration.ofMillis(100))); 23 | } catch (TimeoutException e) { 24 | //scope.shutdown(); 25 | } 26 | System.out.println(task1.state()); // UNAVAILABLE 27 | System.out.println(task2.state()); // UNAVAILABLE 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_14_http_server.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpServer; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStreamWriter; 10 | import java.net.InetSocketAddress; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.List; 14 | import java.util.concurrent.CopyOnWriteArrayList; 15 | import java.util.concurrent.Executors; 16 | import java.util.regex.Pattern; 17 | 18 | import static java.nio.charset.StandardCharsets.UTF_8; 19 | import static java.util.stream.Collectors.joining; 20 | 21 | // A HTTP server that serve static files and answer to 3 services 22 | // GET /tasks returns a list of tasks as a json object 23 | // POST /tasks take the content as a JSON text and add it as a new task 24 | // DELETE /tasks/id delete a task by its id 25 | 26 | // $JAVA_HOME/bin/java -cp target/loom-1.0-SNAPSHOT.jar fr.umlv.loom.example._14_http_server 27 | public interface _14_http_server { 28 | record Task(int id, String content) { 29 | public String toJSON() { 30 | return """ 31 | { 32 | "id": %d, 33 | "content": "%s" 34 | } 35 | """.formatted(id, content); 36 | } 37 | } 38 | 39 | private static void getTasks(HttpExchange exchange, List tasks) throws IOException { 40 | System.err.println("thread " + Thread.currentThread()); 41 | var uri = exchange.getRequestURI(); 42 | System.out.println("GET query " + uri); 43 | 44 | try(exchange) { 45 | var json = tasks.stream() 46 | .map(Task::toJSON) 47 | .collect(joining(", ", "[", "]")); 48 | exchange.getResponseHeaders().set("Content-Type", "application/json"); 49 | exchange.sendResponseHeaders(200, json.length()); 50 | try (var writer = new OutputStreamWriter(exchange.getResponseBody(), UTF_8)) { 51 | writer.write(json); 52 | } 53 | } 54 | } 55 | 56 | Pattern REGEX = Pattern.compile(""" 57 | [^\\:]+:"(.*)"\ 58 | """); 59 | 60 | private static void postTasks(HttpExchange exchange, List tasks) throws IOException { 61 | System.err.println("thread " + Thread.currentThread()); 62 | var uri = exchange.getRequestURI(); 63 | System.out.println("POST query " + uri); 64 | 65 | try (exchange) { 66 | String content; 67 | try (var input = new InputStreamReader(exchange.getRequestBody(), UTF_8); 68 | var reader = new BufferedReader(input)) { 69 | var line = reader.readLine(); 70 | var matcher = REGEX.matcher(line); 71 | matcher.find(); 72 | content = matcher.group(1); 73 | } 74 | System.out.println("content " + content); 75 | var task = new Task(tasks.size(), content); 76 | tasks.add(task); 77 | var json = task.toJSON(); 78 | exchange.getResponseHeaders().set("Content-Type", "application/json"); 79 | exchange.sendResponseHeaders(200, json.length()); 80 | try (var writer = new OutputStreamWriter(exchange.getResponseBody(), UTF_8)) { 81 | writer.write(json); 82 | } 83 | } 84 | } 85 | 86 | private static void deleteTasks(HttpExchange exchange, List tasks) throws IOException { 87 | System.err.println("thread " + Thread.currentThread()); 88 | var uri = exchange.getRequestURI(); 89 | System.out.println("DELETE query " + uri); 90 | 91 | try (exchange) { 92 | var path = Path.of(uri.toString()); 93 | var id = Integer.parseInt(path.getFileName().toString()); 94 | System.out.println("id " + id); 95 | tasks.removeIf(task -> task.id == id); 96 | exchange.sendResponseHeaders(200, 0); 97 | } 98 | } 99 | 100 | private static void getStaticContent(HttpExchange exchange) throws IOException { 101 | System.err.println("thread " + Thread.currentThread()); 102 | var uri = exchange.getRequestURI(); 103 | var path = Path.of(".", uri.toString()); 104 | System.out.println("GET query " + uri + " to " + path); 105 | 106 | try(exchange) { 107 | exchange.sendResponseHeaders(200, Files.size(path)); 108 | Files.copy(path, exchange.getResponseBody()); 109 | } catch(IOException e) { 110 | exchange.sendResponseHeaders(404, 0); 111 | } 112 | } 113 | 114 | static void main(String[] args) throws IOException { 115 | var executor = Executors.newVirtualThreadPerTaskExecutor(); 116 | var localAddress = new InetSocketAddress(8080); 117 | System.out.println("server at http://localhost:" + localAddress.getPort() + "/todo.html"); 118 | 119 | var tasks = new CopyOnWriteArrayList(); 120 | 121 | var server = HttpServer.create(); 122 | server.setExecutor(executor); 123 | server.bind(localAddress, 0); 124 | server.createContext("/", exchange -> getStaticContent(exchange)); 125 | server.createContext("/tasks", exchange -> { 126 | switch (exchange.getRequestMethod()) { 127 | case "GET" -> getTasks(exchange, tasks); 128 | case "POST" -> postTasks(exchange, tasks); 129 | case "DELETE" -> deleteTasks(exchange, tasks); 130 | default -> throw new IllegalStateException("unknown"); 131 | } 132 | }); 133 | server.start(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_15_adhoc_scope.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.io.IOException; 4 | import java.time.Instant; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | import java.util.concurrent.StructuredTaskScope; 8 | import java.util.concurrent.TimeoutException; 9 | import java.util.stream.Stream; 10 | 11 | // $JAVA_HOME/bin/java --enable-preview --add-modules jdk.incubator.concurrent -cp target/loom-1.0-SNAPSHOT.jar fr.umlv.loom.example._15_adhoc_scope 12 | public interface _15_adhoc_scope { 13 | sealed interface Result { } 14 | record Success(T value) implements Result {} 15 | record Failure(Throwable context) implements Result {} 16 | 17 | class StreamStructuredTaskScope extends StructuredTaskScope { 18 | private static final Object POISON = new Object(); 19 | private final BlockingQueue queue = new LinkedBlockingQueue<>(); 20 | 21 | @Override 22 | protected void handleComplete(Subtask future) { 23 | try { 24 | queue.put(switch (future.state()) { 25 | case SUCCESS -> new Success<>(future.get()); 26 | case FAILED -> new Failure<>(future.exception()); 27 | case UNAVAILABLE -> throw new AssertionError(); 28 | }); 29 | } catch (InterruptedException e) { 30 | Thread.currentThread().interrupt(); 31 | } 32 | } 33 | 34 | @Override 35 | public StreamStructuredTaskScope join() throws InterruptedException { 36 | try { 37 | super.join(); 38 | return this; 39 | } finally { 40 | queue.put(POISON); 41 | shutdown(); 42 | } 43 | } 44 | 45 | @Override 46 | public StreamStructuredTaskScope joinUntil(Instant deadline) throws InterruptedException, TimeoutException { 47 | try { 48 | super.joinUntil(deadline); 49 | return this; 50 | } finally { 51 | queue.put(POISON); 52 | shutdown(); 53 | } 54 | } 55 | 56 | @SuppressWarnings("unchecked") 57 | public Stream> stream() { 58 | return Stream.of((Object) null).mapMulti((__, consumer) -> { 59 | try { 60 | Object result; 61 | while((result = queue.take()) != POISON) { 62 | consumer.accept((Result) result); 63 | } 64 | } catch (InterruptedException e) { 65 | Thread.currentThread().interrupt(); 66 | } 67 | }); 68 | } 69 | } 70 | 71 | static void main(String[] args) throws InterruptedException { 72 | try(var scope = new StreamStructuredTaskScope<>()) { 73 | var task1 = scope.fork(() -> { 74 | Thread.sleep(50); 75 | return 1; 76 | }); 77 | var task2 = scope.fork(() -> { 78 | Thread.sleep(50); 79 | throw new IOException("oops"); 80 | }); 81 | scope.join().stream().forEach(System.out::println); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_1_starting_thread.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | // $JAVA_HOME/bin/java -cp target/classes fr.umlv.loom.example._1_starting_thread 4 | public interface _1_starting_thread { 5 | static void main(String[] args) throws InterruptedException { 6 | // platform threads 7 | var pthread = new Thread(() -> { 8 | System.out.println("platform " + Thread.currentThread()); 9 | }); 10 | pthread.start(); 11 | pthread.join(); 12 | 13 | // virtual threads 14 | var vthread = Thread.startVirtualThread(() -> { 15 | System.out.println("virtual " + Thread.currentThread()); 16 | }); 17 | vthread.join(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_21_shutdown_on_success.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import fr.umlv.loom.structured.StructuredScopeShutdownOnSuccess; 4 | 5 | import java.io.IOException; 6 | 7 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._21_shutdown_on_success 8 | public interface _21_shutdown_on_success { 9 | static void main(String[] args) throws IOException, InterruptedException { 10 | try (var scope = new StructuredScopeShutdownOnSuccess()) { 11 | scope.fork(() -> { 12 | Thread.sleep(1_000); 13 | return 1; 14 | }); 15 | scope.fork(() -> { 16 | Thread.sleep(42); 17 | return 2; 18 | }); 19 | var result = scope.joinAll(); 20 | System.out.println(result); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_22_shutdown_on_failure.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import fr.umlv.loom.structured.StructuredScopeShutdownOnFailure; 4 | 5 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._22_shutdown_on_failure 6 | public interface _22_shutdown_on_failure { 7 | static void main(String[] args) throws InterruptedException { 8 | try (var scope = new StructuredScopeShutdownOnFailure()) { 9 | var supplier1 = scope.fork(() -> { 10 | Thread.sleep(1_000); 11 | return 1; 12 | }); 13 | var supplier2 = scope.fork(() -> { 14 | Thread.sleep(42); 15 | return 2; 16 | }); 17 | scope.joinAll(); 18 | System.out.println(supplier1.get() + supplier2.get()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_23_as_stream.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import fr.umlv.loom.structured.StructuredScopeAsStream; 4 | import fr.umlv.loom.structured.StructuredScopeAsStream.Result; 5 | 6 | import java.io.IOException; 7 | import java.util.stream.Collectors; 8 | 9 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._23_as_stream 10 | public interface _23_as_stream { 11 | static void main(String[] args) throws /*IOException,*/ InterruptedException { 12 | try (var scope = new StructuredScopeAsStream()) { 13 | scope.fork(() -> { 14 | Thread.sleep(1_000); 15 | return 1; 16 | }); 17 | scope.fork(() -> { 18 | Thread.sleep(42); 19 | throw new IOException(); 20 | //return 2; 21 | }); 22 | //var list = scope.joinAll(stream -> stream.toList()); 23 | //System.out.println(list); 24 | 25 | var result = scope.joinAll(s -> s.collect(Result.toResult(Collectors.summingInt(v -> v)))); 26 | //System.out.println(result.get()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_24_as_stream_short_circuit.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import fr.umlv.loom.structured.StructuredScopeAsStream; 4 | import fr.umlv.loom.structured.StructuredScopeAsStream.Result; 5 | 6 | import java.io.IOException; 7 | 8 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._24_as_stream_short_circuit 9 | public interface _24_as_stream_short_circuit { 10 | static void main(String[] args) throws InterruptedException { 11 | try (var scope = new StructuredScopeAsStream()) { 12 | scope.fork(() -> { 13 | Thread.sleep(1_000); 14 | return 1_000; 15 | }); 16 | scope.fork(() -> { 17 | Thread.sleep(42); 18 | //throw new IOException(); 19 | return 42; 20 | }); 21 | var optional = 22 | scope.joinAll(s -> s.flatMap(Result::keepOnlySuccess).findFirst()); 23 | System.out.println(optional); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_2_thread_builder.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | // $JAVA_HOME/bin/java -cp target/classes fr.umlv.loom.example._2_thread_builder 4 | public interface _2_thread_builder { 5 | static void main(String[] args) throws InterruptedException { 6 | // platform thread 7 | var pthread = Thread.ofPlatform() 8 | .name("platform-", 0) 9 | .start(() -> { 10 | System.out.println("platform " + Thread.currentThread()); 11 | }); 12 | pthread.join(); 13 | 14 | // virtual thread 15 | var vthread = Thread.ofVirtual() 16 | .name("virtual-", 0) 17 | .start(() -> { 18 | System.out.println("virtual " + Thread.currentThread()); 19 | }); 20 | vthread.join(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_3_how_many_platform_thread.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.concurrent.BrokenBarrierException; 4 | import java.util.concurrent.CountDownLatch; 5 | import java.util.concurrent.CyclicBarrier; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.stream.IntStream; 8 | 9 | // $JAVA_HOME/bin/java -cp target/classes fr.umlv.loom.example._3_how_many_platform_thread 10 | public interface _3_how_many_platform_thread { 11 | static void main(String[] args) throws BrokenBarrierException, InterruptedException { 12 | var barrier = new CyclicBarrier(100_000); 13 | var threads = IntStream.range(0, 100_000) 14 | .mapToObj(i -> new Thread(() -> { 15 | try { 16 | //Thread.sleep(5_000); 17 | barrier.await(); 18 | } catch (InterruptedException | BrokenBarrierException e) { 19 | throw new AssertionError(e); 20 | } 21 | })) 22 | .toList(); 23 | var i = 0; 24 | for (var thread: threads) { 25 | System.out.println(i++); 26 | thread.start(); 27 | } 28 | for (var thread : threads) { 29 | thread.join(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_4_how_many_virtual_thread.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.concurrent.BrokenBarrierException; 4 | import java.util.concurrent.CyclicBarrier; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import java.util.stream.IntStream; 7 | 8 | // $JAVA_HOME/bin/java -cp target/classes fr.umlv.loom.example._4_how_many_virtual_thread 9 | public interface _4_how_many_virtual_thread { 10 | static void main(String[] args) throws InterruptedException { 11 | var counter = new AtomicInteger(); 12 | var barrier = new CyclicBarrier(100_000); 13 | var threads = IntStream.range(0, 100_000) 14 | .mapToObj(i -> Thread.ofVirtual().unstarted(() -> { 15 | try { 16 | //Thread.sleep(5_000); 17 | barrier.await(); 18 | } catch (InterruptedException | BrokenBarrierException e) { 19 | throw new AssertionError(e); 20 | } 21 | counter.incrementAndGet(); 22 | })) 23 | .toList(); 24 | for (var thread : threads) { 25 | thread.start(); 26 | } 27 | for (var thread : threads) { 28 | thread.join(); 29 | } 30 | System.out.println(counter); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_5_continuation.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import jdk.internal.vm.Continuation; 4 | import jdk.internal.vm.ContinuationScope; 5 | 6 | // $JAVA_HOME/bin/java --add-exports java.base/jdk.internal.vm=ALL-UNNAMED -cp target/classes fr.umlv.loom.example._5_continuation 7 | public interface _5_continuation { 8 | static void main(String[] args) { 9 | var scope = new ContinuationScope("hello"); 10 | var continuation = new Continuation(scope, () -> { 11 | System.out.println("C1"); 12 | Continuation.yield(scope); 13 | System.out.println("C2"); 14 | Continuation.yield(scope); 15 | System.out.println("C3"); 16 | }); 17 | 18 | System.out.println("start"); 19 | continuation.run(); 20 | System.out.println("came back"); 21 | continuation.run(); 22 | System.out.println("back again"); 23 | continuation.run(); 24 | System.out.println("back again again"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_6_thread_local.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | // $JAVA_HOME/bin/java -cp target/classes fr.umlv.loom.example._6_thread_local 4 | public class _6_thread_local { 5 | private static final ThreadLocal USER = new ThreadLocal<>(); 6 | 7 | private static void sayHello() { 8 | System.out.println("Hello " + USER.get()); 9 | } 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | var vthread = Thread.ofVirtual().start(() -> { 13 | USER.set("Bob"); 14 | try { 15 | sayHello(); 16 | } finally { 17 | USER.remove(); 18 | } 19 | }); 20 | 21 | vthread.join(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_7_scoped_value.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._7_scoped_value 4 | public class _7_scoped_value { 5 | private static final ScopedValue USER = ScopedValue.newInstance(); 6 | 7 | private static void sayHello() { 8 | System.out.println("Hello " + USER.get()); 9 | } 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | var vthread = Thread.ofVirtual() 13 | .start(() -> { 14 | ScopedValue.runWhere(USER, "Bob", () -> { 15 | sayHello(); 16 | }); 17 | }); 18 | 19 | vthread.join(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_8_executor.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.Executors; 6 | 7 | // $JAVA_HOME/bin/java --enable-preview -cp target/loom-1.0-SNAPSHOT.jar fr.umlv.loom.example._8_executor 8 | public interface _8_executor { 9 | private static void simple() throws ExecutionException, InterruptedException { 10 | try(var executor = Executors.newVirtualThreadPerTaskExecutor()) { 11 | 12 | var future1 = executor.submit(() -> { 13 | Thread.sleep(10); 14 | //return 1; 15 | throw new IOException("oops"); 16 | }); 17 | var future2 = executor.submit(() -> { 18 | Thread.sleep(1_000); 19 | return 2; 20 | }); 21 | executor.shutdown(); 22 | var result = future1.get() + future2.get(); 23 | System.out.println(result); 24 | } 25 | } 26 | 27 | static void main(String[] args) throws ExecutionException, InterruptedException { 28 | simple(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/example/_9_structured_concurrency.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.example; 2 | 3 | import java.util.concurrent.StructuredTaskScope; 4 | 5 | // $JAVA_HOME/bin/java --enable-preview -cp target/classes fr.umlv.loom.example._9_structured_concurrency 6 | public interface _9_structured_concurrency { 7 | static void main(String[] args) throws InterruptedException { 8 | try (var scope = new StructuredTaskScope()) { 9 | var task1 = scope.fork(() -> { 10 | Thread.sleep(1_000); 11 | return 1; 12 | }); 13 | var task2 = scope.fork(() -> { 14 | Thread.sleep(1_000); 15 | return 2; 16 | }); 17 | scope.join(); 18 | var result = task1.get() + task2.get(); 19 | System.out.println(result); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/executor/UnsafeExecutors.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.executor; 2 | 3 | import java.lang.Thread.UncaughtExceptionHandler; 4 | import java.lang.invoke.MethodHandle; 5 | import java.lang.invoke.MethodHandles; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.util.Objects; 9 | import java.util.concurrent.Executor; 10 | 11 | import static java.lang.invoke.MethodHandles.insertArguments; 12 | import static java.lang.invoke.MethodType.methodType; 13 | 14 | public class UnsafeExecutors { 15 | private static class BTB { 16 | private String name; 17 | private long counter; 18 | private int characteristics; 19 | private UncaughtExceptionHandler uhe; 20 | } 21 | private static class VTB extends BTB { 22 | private Executor executor; 23 | } 24 | 25 | private static final MethodHandle SET_EXECUTOR; 26 | static { 27 | try { 28 | var unsafeClass = Class.forName("sun.misc.Unsafe"); 29 | var unsafeField = unsafeClass.getDeclaredField("theUnsafe"); 30 | unsafeField.setAccessible(true); 31 | var unsafe = unsafeField.get(null); 32 | var objectFieldOffset = unsafeClass.getMethod("objectFieldOffset", Field.class); 33 | var executorField = VTB.class.getDeclaredField("executor"); 34 | executorField.setAccessible(true); 35 | var executorOffset = (long) objectFieldOffset.invoke(unsafe, executorField); 36 | var putObject = MethodHandles.lookup() 37 | .findVirtual(unsafeClass, "putObject", methodType(void.class, Object.class, long.class, Object.class)); 38 | var setExecutor = insertArguments(insertArguments(putObject, 2, executorOffset), 0, unsafe); 39 | SET_EXECUTOR = setExecutor; 40 | } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 41 | throw new AssertionError(e); 42 | } 43 | } 44 | 45 | private static void setExecutor(Object builder, Object executor) { 46 | try { 47 | SET_EXECUTOR.invokeExact(builder, executor); 48 | } catch (Throwable e) { 49 | throw new AssertionError(e); 50 | } 51 | } 52 | 53 | private static class VirtualThreadExecutor implements Executor { 54 | private final Executor executor; 55 | 56 | public VirtualThreadExecutor(Executor executor) { 57 | this.executor = executor; 58 | } 59 | 60 | @Override 61 | public void execute(Runnable command) { 62 | var builder = Thread.ofVirtual(); 63 | setExecutor(builder, executor); 64 | builder.start(command); 65 | } 66 | } 67 | 68 | public static B configureBuilderExecutor(B builder, Executor executor) { 69 | if (executor != null) { 70 | setExecutor(builder, executor); 71 | } 72 | return builder; 73 | } 74 | 75 | public static Executor virtualThreadExecutor(Executor executor) { 76 | Objects.requireNonNull(executor); 77 | return new VirtualThreadExecutor(executor); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/executor/VirtualThreadExecutor.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.executor; 2 | 3 | /* 4 | import java.lang.invoke.MethodHandles; 5 | import java.lang.invoke.VarHandle; 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.ExecutionException; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.Future; 15 | import java.util.concurrent.RejectedExecutionException; 16 | import java.util.concurrent.StructuredExecutor; 17 | import java.util.concurrent.StructuredExecutor.ShutdownOnSuccess; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.concurrent.TimeoutException; 20 | 21 | public final class VirtualThreadExecutor implements ExecutorService { 22 | private static final VarHandle STATE; 23 | static { 24 | var lookup = MethodHandles.lookup(); 25 | try { 26 | STATE = lookup.findVarHandle(VirtualThreadExecutor.class, "state", int.class); 27 | } catch (NoSuchFieldException | IllegalAccessException e) { 28 | throw new AssertionError(e); 29 | } 30 | } 31 | 32 | private final StructuredExecutor executor; 33 | private final ExecutorService executorOfExecutor = Executors.newSingleThreadExecutor(); 34 | private volatile int state; 35 | 36 | private static final int RUNNING = 0; 37 | private static final int SHUTDOWN = 1; 38 | private static final int TERMINATED = 2; 39 | 40 | public VirtualThreadExecutor() { 41 | executor = postSync(StructuredExecutor::open); 42 | } 43 | 44 | private void checkShutdownState() { 45 | if (state >= SHUTDOWN) { 46 | throw new RejectedExecutionException(); 47 | } 48 | } 49 | 50 | @SuppressWarnings("unchecked") // very wrong but works 51 | private static AssertionError rethrow(Throwable cause) throws T { 52 | throw (T) cause; 53 | } 54 | 55 | private void postAsync(Runnable runnable) { 56 | executorOfExecutor.execute(runnable); 57 | } 58 | 59 | private V postSync(Callable callable) { 60 | var future = executorOfExecutor.submit(callable); 61 | try { 62 | return future.get(); 63 | } catch (ExecutionException e) { 64 | var cause = e.getCause(); 65 | throw rethrow(cause); 66 | } catch (InterruptedException e) { 67 | throw rethrow(e); 68 | } 69 | } 70 | 71 | @Override 72 | public void shutdown() { 73 | if (STATE.compareAndSet(this, RUNNING, SHUTDOWN)) { 74 | postAsync(() -> { 75 | if (state == TERMINATED) { 76 | return; 77 | } 78 | try { 79 | executor.join(); 80 | } catch (InterruptedException e) { 81 | throw new AssertionError(e); 82 | } 83 | state = TERMINATED; 84 | executor.close(); 85 | executorOfExecutor.shutdown(); 86 | }); 87 | } 88 | } 89 | 90 | @Override 91 | public List shutdownNow() { 92 | if (STATE.compareAndSet(this, RUNNING, SHUTDOWN)) { 93 | postSync(() -> { 94 | if (state == TERMINATED) { 95 | return null; 96 | } 97 | executor.shutdown(); 98 | executor.join(); 99 | state = TERMINATED; 100 | executor.close(); 101 | executorOfExecutor.shutdown(); 102 | return null; 103 | }); 104 | } 105 | return List.of(); 106 | } 107 | 108 | @Override 109 | public boolean isShutdown() { 110 | return state >= SHUTDOWN; 111 | } 112 | 113 | @Override 114 | public boolean isTerminated() { 115 | return state == TERMINATED; 116 | } 117 | 118 | @Override 119 | public boolean awaitTermination(long timeout, TimeUnit unit) { 120 | if (state == TERMINATED) { 121 | return false; 122 | } 123 | var deadline = Instant.now().plus(Duration.ofNanos(unit.toNanos(timeout))); 124 | return postSync(() -> { 125 | if (state == TERMINATED) { 126 | return false; 127 | } 128 | var result = true; 129 | try { 130 | executor.joinUntil(deadline); 131 | } catch (TimeoutException e) { 132 | result = false; 133 | } 134 | executor.shutdown(); 135 | state = TERMINATED; 136 | executor.close(); 137 | executorOfExecutor.shutdown(); 138 | return result; 139 | }); 140 | } 141 | 142 | @Override 143 | public void execute(Runnable command) { 144 | submit(command); 145 | } 146 | 147 | @Override 148 | public Future submit(Callable task) { 149 | checkShutdownState(); 150 | return postSync(() -> { 151 | checkShutdownState(); 152 | return executor.fork(task); 153 | }); 154 | } 155 | 156 | @Override 157 | public Future submit(Runnable task, T result) { 158 | return submit(() -> { 159 | task.run(); 160 | return result; 161 | }); 162 | } 163 | 164 | @Override 165 | public Future submit(Runnable task) { 166 | return submit(task, null); 167 | } 168 | 169 | @Override 170 | public List> invokeAll(Collection> tasks) throws InterruptedException { 171 | checkShutdownState(); 172 | return postSync(() -> { 173 | checkShutdownState(); 174 | try(var executor = StructuredExecutor.open()) { 175 | var list = tasks.stream().map(executor::fork).toList(); 176 | executor.join(); 177 | return list; 178 | } 179 | }); 180 | } 181 | 182 | @Override 183 | public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { 184 | checkShutdownState(); 185 | var deadline = Instant.now().plus(Duration.ofNanos(unit.toNanos(timeout))); 186 | return postSync(() -> { 187 | checkShutdownState(); 188 | try(var executor = StructuredExecutor.open()) { 189 | var futures = tasks.stream().map(executor::fork).toList(); 190 | try { 191 | executor.joinUntil(deadline); 192 | } catch(TimeoutException e) { 193 | return futures.stream().filter(Future::isDone).toList(); 194 | } 195 | return futures; 196 | } 197 | }); 198 | } 199 | 200 | @Override 201 | public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { 202 | checkShutdownState(); 203 | return postSync(() -> { 204 | checkShutdownState(); 205 | try(var executor = StructuredExecutor.open()) { 206 | var handler = new ShutdownOnSuccess(); 207 | tasks.forEach(callable -> executor.fork(callable, handler)); 208 | executor.join(); 209 | return handler.result(); 210 | } 211 | }); 212 | } 213 | 214 | @Override 215 | public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 216 | checkShutdownState(); 217 | var deadline = Instant.now().plus(Duration.ofNanos(unit.toNanos(timeout))); 218 | return postSync(() -> { 219 | checkShutdownState(); 220 | try(var executor = StructuredExecutor.open()) { 221 | var handler = new ShutdownOnSuccess(); 222 | tasks.forEach(callable -> executor.fork(callable, handler)); 223 | executor.joinUntil(deadline); 224 | return handler.result(); 225 | } 226 | }); 227 | } 228 | } 229 | */ 230 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/oldstructured/AsyncScope2.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.oldstructured; 2 | 3 | import java.time.Instant; 4 | import java.util.function.Function; 5 | import java.util.stream.Stream; 6 | 7 | /** 8 | * A scope to execute asynchronous tasks in way that appears to be synchronous. 9 | *

10 | * The API is separated into different phases 11 | *

    12 | *
  1. Create the async scope with either {@link AsyncScope2#ordered()} or 13 | * if you don't care about the order with {@link AsyncScope2#unordered()}. 14 | *
  2. Execute a task using {@link #async(Task)} 15 | *
  3. Optionally configure how to react to checked exceptions using 16 | * {@link AsyncScope2#recover(ExceptionHandler)} or set a deadline with {@link #deadline(Instant)}. 17 | *
  4. Gather the results of the tasks in a stream with 18 | * {@link AsyncScope2#await(Function)}. 19 | *
20 | * 21 | * Using {@link #ordered()} is equivalent to a loop over all the tasks and calling them one by one 22 | * to get the results. Thus, the results are available in order. 23 | * Using {@link #unordered()} relax the ordering constraint allowing the results to be processed 24 | * * as soon as they are available. 25 | *

26 | * Exceptions are automatically propagated from the tasks to the method {@link #await(Function)} and 27 | * if an exception occurs it cancels all the remaining tasks. 28 | *

29 | * The method {@link #recover(ExceptionHandler)} recovers from checked exceptions (exceptions that are 30 | * not subclasses of {@link RuntimeException}) either by returning a value instead or by 31 | * propagating another exception. 32 | *

33 | * Here is a simple example using {@link #ordered()}, despite the fact that the first 34 | * task take longer to complete, the results of the tasks are available in order. 35 | *

 36 |  *   List<Integer> list;
 37 |  *   try(var scope = AsyncScope2.<Integer, RuntimeException>ordered()) {
 38 |  *       scope.async(() -> {
 39 |  *         Thread.sleep(200);
 40 |  *         return 10;
 41 |  *       });
 42 |  *       scope.async(() -> 20);
 43 |  *       list = scope.await(Stream::toList);  // [10, 20]
 44 |  *   }
 45 |  * 
46 | * 47 | * and an example using {@link #unordered()}, here, the results are available 48 | * in completion order. 49 | *
 50 |  *   List<Integer> list;
 51 |  *   try(var scope = AsyncScope2.<Integer, RuntimeException>unordered()) {
 52 |  *       scope.async(() -> {
 53 |  *         Thread.sleep(200);
 54 |  *         return 10;
 55 |  *       });
 56 |  *       scope.async(() -> 20);
 57 |  *       list = scope.await(Stream::toList);  // [20, 10]
 58 |  *   }
 59 |  * 
60 | * 61 | * 62 | * @param type of task values 63 | * @param type of the checked exception or {@link RuntimeException} otherwise 64 | */ 65 | public sealed interface AsyncScope2 extends AutoCloseable permits AsyncScope2Impl { 66 | /** 67 | * Creates an async scope with that receives the results of tasks in the order of the calls to {@link #async(Task)}. 68 | * 69 | * @param type of task values 70 | * @param type of the checked exception or {@link RuntimeException} otherwise 71 | */ 72 | static AsyncScope2 ordered() { 73 | return AsyncScope2Impl.of(true); 74 | } 75 | 76 | /** 77 | * Creates an async scope that receives the results of tasks out of order. 78 | * 79 | * @param type of task values 80 | * @param type of the checked exception or {@link RuntimeException} otherwise 81 | */ 82 | static AsyncScope2 unordered() { 83 | return AsyncScope2Impl.of(false); 84 | } 85 | 86 | /** 87 | * Task to execute. 88 | * @param type of the return value 89 | * @param type of the checked exception or {@link RuntimeException} otherwise 90 | * 91 | * @see #async(Task) 92 | */ 93 | interface Task { 94 | /** 95 | * Compute a value. 96 | * @return the result of the computation 97 | * @throws E a checked exception 98 | * @throws InterruptedException if the task is interrupted 99 | */ 100 | R compute() throws E, InterruptedException; 101 | } 102 | 103 | /** 104 | * Execute a task asynchronously. 105 | * @param task the task to execute 106 | * @throws IllegalStateException if the lambda captures a value of a mutable class while assertions are enabled 107 | * 108 | * @see #await(Function) 109 | */ 110 | AsyncScope2 async(Task task); 111 | 112 | /** 113 | * A handler of checked exceptions. 114 | * @param type of the exception raised by the tasks 115 | * @param type of the result of a task 116 | * @param type of new exception if a new exception is raised, {@link RuntimeException} otherwise 117 | * 118 | * @see #recover(ExceptionHandler) 119 | */ 120 | interface ExceptionHandler { 121 | /** 122 | * Called to react to a checked exception 123 | * @param exception the checked exception raised by a task 124 | * @return the value that is used as result instead of the exception 125 | * @throws F the new exception raised 126 | */ 127 | R handle(E exception) throws F; 128 | } 129 | 130 | /** 131 | * Configures an exception handler to recover from the checked exceptions potentially raised by the tasks. 132 | * This method can not intercept {@link RuntimeException} or {@link Error}, 133 | * those will stop the method {@link #await(Function)} to complete. 134 | * This method is an intermediary method that configure the async scope, 135 | * the handler will be used when {@link #await(Function)} is called. 136 | * 137 | * @param handler the exception handler 138 | * @return a new async scope 139 | * @param the type of the new exception if the exception raised by a task is wrapped 140 | * @throws IllegalStateException if an exception handler is already configured 141 | */ 142 | AsyncScope2 recover(ExceptionHandler handler); 143 | 144 | /** 145 | * Exception thrown if the deadline set by {@link #deadline(Instant) deadline} is reached. 146 | */ 147 | final class DeadlineException extends RuntimeException { 148 | /** 149 | * Creates a DeadlineException with a message. 150 | * @param message the message of the exception 151 | */ 152 | public DeadlineException(String message) { 153 | super(message); 154 | } 155 | 156 | /** 157 | * Creates a DeadlineException with a message and a cause. 158 | * @param message the message of the exception 159 | * @param cause the cause of the exception 160 | */ 161 | public DeadlineException(String message, Throwable cause) { 162 | super(message, cause); 163 | } 164 | 165 | /** 166 | * Creates a DeadlineException with a cause. 167 | * @param cause the cause of the exception 168 | */ 169 | public DeadlineException(Throwable cause) { 170 | super(cause); 171 | } 172 | } 173 | 174 | /** 175 | * Configures a deadline for the whole computation. 176 | * If the deadline time is less than the current time, the deadline is ignored. 177 | * This method is an intermediary method that configure the async scope, 178 | * the deadline will be set when {@link #await(Function)} is called. 179 | * 180 | * @param deadline the timeout deadline 181 | * @return the configured option 182 | * @throws IllegalStateException if a deadline is already configured 183 | * 184 | * @see #await(Function) 185 | * @see DeadlineException 186 | */ 187 | AsyncScope2 deadline(Instant deadline); 188 | 189 | /** 190 | * Propagates the results of the asynchronous tasks as a stream to process them. 191 | * This method may block if the stream requires elements that are not yet available. 192 | * 193 | * When this method returns, all tasks either complete normally or are cancelled because 194 | * the result value is not necessary for the stream computation. 195 | * 196 | * @param streamMapper function called with a stream to return the result of the whole computation 197 | * @return the result of the whole computation 198 | * @param the type of the result 199 | * @throws E the type of the checked exception, {@link RuntimeException} otherwise 200 | * @throws InterruptedException if the current thread or any threads running a task is interrupted 201 | * @throws DeadlineException if the {@link #deadline(Instant) deadline} is reached 202 | */ 203 | T await(Function, ? extends T> streamMapper) throws E, DeadlineException, InterruptedException; 204 | 205 | /** 206 | * Closes the async scope and if necessary make sure that any dangling asynchronous tasks are cancelled. 207 | * This method is idempotent. 208 | */ 209 | @Override 210 | void close(); 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/oldstructured/AsyncScope2Impl.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.oldstructured; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.time.Duration; 5 | import java.time.Instant; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.Spliterator; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.ExecutorCompletionService; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | import java.util.concurrent.Future; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.TimeoutException; 17 | import java.util.function.Consumer; 18 | import java.util.function.Function; 19 | import java.util.stream.Stream; 20 | import java.util.stream.StreamSupport; 21 | 22 | record AsyncScope2Impl( 23 | ExecutorService executorService, 24 | ExecutorCompletionService completionService, 25 | ArrayList> futures, 26 | AsyncScope2.ExceptionHandler handler, 27 | Instant deadline 28 | ) implements AsyncScope2 { 29 | 30 | private static final boolean ASSERTION_ENABLED; 31 | static { 32 | var assertionEnabled = false; 33 | //noinspection AssertWithSideEffects 34 | assert assertionEnabled = true; 35 | ASSERTION_ENABLED = assertionEnabled; 36 | } 37 | 38 | private static final class Debug { 39 | private static final ClassValue CLASS_VALUE = new ClassValue<>() { 40 | @Override 41 | protected String computeValue(Class type) { 42 | return isClassAllowed(type); 43 | } 44 | }; 45 | 46 | private static String isClassAllowed(Class type) { 47 | if (!Modifier.isFinal(type.getModifiers())) { 48 | return type.getName() + " is not final"; 49 | } 50 | for(Class t = type; t != Object.class; t = t.getSuperclass()) { 51 | for (var field : t.getDeclaredFields()) { 52 | var modifiers = field.getModifiers(); 53 | if (Modifier.isStatic(modifiers) || Modifier.isVolatile(modifiers)) { 54 | continue; 55 | } 56 | if (!Modifier.isFinal(modifiers)) { 57 | return field + " is not declared final or volatile"; 58 | } 59 | var result = isTypeAllowed(field.getType()); 60 | if (result != null) { 61 | return "for field " + field + ", " + result; 62 | } 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | private static String isTypeAllowed(Class type) { 69 | if (type.isRecord()) { 70 | for(var component: type.getRecordComponents()) { 71 | var result = isTypeAllowed(component.getType()); 72 | if (result != null) { 73 | return "for component " + component + ", " + result; 74 | } 75 | } 76 | return null; 77 | } 78 | if (type.isPrimitive()) { 79 | return null; 80 | } 81 | var packageName = type.getPackageName(); 82 | if (packageName.equals("java.time") 83 | || packageName.equals("java.util.concurrent") 84 | || packageName.equals("java.util.concurrent.atomic") 85 | || packageName.equals("java.util.concurrent.locks")) { 86 | return null; 87 | } 88 | return switch (type.getName()) { 89 | case "java.lang.String", 90 | "java.lang.Void", "java.lang.Boolean", "java.lang.Byte", "java.lang.Character", "java.lang.Short", 91 | "java.lang.Integer", "java.lang.Long", "java.lang.Float", "java.lang.Double", 92 | "java.util.Random" -> null; 93 | default -> CLASS_VALUE.get(type); 94 | }; 95 | } 96 | 97 | private static void checkAsyncTaskUseOnlyImmutableData(Class clazz) { 98 | var result = CLASS_VALUE.get(clazz); 99 | if (result != null) { 100 | throw new IllegalStateException("lambda " + clazz.getName()+ " captures modifiable state " + result); 101 | } 102 | } 103 | } 104 | 105 | public static AsyncScope2 of(boolean ordered) { 106 | var executorService = Executors.newVirtualThreadPerTaskExecutor(); 107 | var completionService = ordered? null: new ExecutorCompletionService(executorService); 108 | return new AsyncScope2Impl<>(executorService, completionService, new ArrayList<>(), null, null); 109 | } 110 | 111 | @Override 112 | public AsyncScope2 async(Task task) { 113 | if (executorService.isShutdown()) { 114 | throw new IllegalStateException("result already called"); 115 | } 116 | if (ASSERTION_ENABLED) { 117 | Debug.checkAsyncTaskUseOnlyImmutableData(task.getClass()); 118 | } 119 | if (completionService != null) { 120 | futures.add(completionService.submit(task::compute)); 121 | } else { 122 | futures.add(executorService.submit(task::compute)); 123 | } 124 | return this; 125 | } 126 | 127 | @Override 128 | @SuppressWarnings("unchecked") 129 | public AsyncScope2 recover(ExceptionHandler handler) { 130 | Objects.requireNonNull(handler); 131 | if (executorService.isShutdown()) { 132 | throw new IllegalStateException("result already called"); 133 | } 134 | if (this.handler != null) { 135 | throw new IllegalStateException("handler already set"); 136 | } 137 | return new AsyncScope2Impl<>(executorService, completionService, futures, (ExceptionHandler) handler, deadline); 138 | } 139 | 140 | @Override 141 | public AsyncScope2 deadline(Instant deadline) { 142 | Objects.requireNonNull(deadline); 143 | if (executorService.isShutdown()) { 144 | throw new IllegalStateException("result already called"); 145 | } 146 | if (this.deadline != null) { 147 | throw new IllegalStateException("deadline already set"); 148 | } 149 | return new AsyncScope2Impl<>(executorService, completionService, futures, handler, deadline); 150 | } 151 | 152 | @Override 153 | public T await(Function, ? extends T> streamMapper) throws E, DeadlineException, InterruptedException { 154 | if (executorService.isShutdown()) { 155 | throw new IllegalStateException("result already called"); 156 | } 157 | executorService.shutdown(); 158 | 159 | var spliterator = completionService == null? 160 | orderedSpliterator(executorService, futures, handler, deadline): 161 | unorderedSpliterator(executorService, completionService, futures, handler, deadline); 162 | var stream = StreamSupport.stream(spliterator, false); 163 | return streamMapper.apply(stream); 164 | } 165 | 166 | @Override 167 | public void close() { 168 | if (!executorService.isTerminated()) { 169 | executorService.shutdownNow(); 170 | try { 171 | while(!executorService.awaitTermination(1, TimeUnit.DAYS)) { 172 | // empty 173 | } 174 | } catch (InterruptedException e) { 175 | Thread.currentThread().interrupt(); 176 | } 177 | } 178 | } 179 | 180 | 181 | // Utilities 182 | // this code is duplicated in AsyncMonadImpl 183 | 184 | private static Spliterator orderedSpliterator(ExecutorService executorService, List> futures, ExceptionHandler handler, Instant deadline) { 185 | return new Spliterator<>() { 186 | private int index; 187 | 188 | @Override 189 | public boolean tryAdvance(Consumer action) { 190 | if (index == futures.size()) { 191 | return false; 192 | } 193 | var future = futures.get(index); 194 | try { 195 | R result; 196 | try { 197 | result = deadline == null? 198 | future.get(): 199 | future.get(timeoutInNanos(deadline), TimeUnit.NANOSECONDS); 200 | } catch (ExecutionException e) { 201 | result = handleException(e.getCause(), handler); 202 | } catch (TimeoutException e) { 203 | throw new DeadlineException("deadline reached", e); 204 | } 205 | action.accept(result); 206 | } catch (Exception e) { 207 | executorService.shutdownNow(); 208 | throw rethrow(e); 209 | } 210 | index++; 211 | return true; 212 | } 213 | 214 | @Override 215 | public Spliterator trySplit() { 216 | return null; 217 | } 218 | @Override 219 | public long estimateSize() { 220 | return futures.size() - index; 221 | } 222 | @Override 223 | public int characteristics() { 224 | return IMMUTABLE | SIZED | ORDERED; 225 | } 226 | }; 227 | } 228 | 229 | private static Spliterator unorderedSpliterator(ExecutorService executorService, ExecutorCompletionService completionService, List> futures, ExceptionHandler handler, Instant deadline) { 230 | return new Spliterator<>() { 231 | private int index; 232 | 233 | @Override 234 | public boolean tryAdvance(Consumer action) { 235 | if (index == futures.size()) { 236 | return false; 237 | } 238 | try { 239 | var future = deadline == null? 240 | completionService.take(): 241 | completionService.poll(timeoutInNanos(deadline), TimeUnit.NANOSECONDS); 242 | if (future == null) { 243 | throw new DeadlineException("deadline reached"); 244 | } 245 | R result; 246 | try { 247 | result = future.get(); 248 | } catch (ExecutionException e) { 249 | result = handleException(e.getCause(), handler); 250 | } 251 | action.accept(result); 252 | index++; 253 | return true; 254 | } catch (Exception e) { 255 | executorService.shutdownNow(); 256 | throw rethrow(e); 257 | } 258 | } 259 | 260 | @Override 261 | public Spliterator trySplit() { 262 | return null; 263 | } 264 | @Override 265 | public long estimateSize() { 266 | return futures.size() - index; 267 | } 268 | @Override 269 | public int characteristics() { 270 | return IMMUTABLE | SIZED; 271 | } 272 | }; 273 | } 274 | 275 | private static long timeoutInNanos(Instant deadline) { 276 | var timeout = Duration.between(Instant.now(), deadline).toNanos(); 277 | return timeout < 0? 0: timeout; 278 | } 279 | 280 | private static R handleException(Throwable cause, ExceptionHandler handler) throws InterruptedException { 281 | if (cause instanceof RuntimeException e) { 282 | throw e; 283 | } 284 | if (cause instanceof Error e) { 285 | throw e; 286 | } 287 | if (cause instanceof InterruptedException e) { 288 | throw e; 289 | } 290 | if (cause instanceof Exception e) { 291 | if (handler != null) { 292 | R newValue; 293 | try { 294 | newValue = handler.handle(e); 295 | } catch (Exception newException) { 296 | throw rethrow(newException); 297 | } 298 | return newValue; 299 | } 300 | } 301 | throw rethrow(cause); 302 | } 303 | 304 | @SuppressWarnings("unchecked") // very wrong but works 305 | private static AssertionError rethrow(Throwable cause) throws T { 306 | throw (T) cause; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/proxy/Host.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | class Host { 4 | static final String NAME = "www.example.com"; 5 | static final int PORT = 80; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/proxy/TCPPlatformThreadProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.ServerSocketChannel; 9 | import java.nio.channels.SocketChannel; 10 | import java.util.concurrent.Executors; 11 | 12 | public class TCPPlatformThreadProxy { 13 | private static Runnable runnable(SocketChannel socket1, SocketChannel socket2) { 14 | return () -> { 15 | var buffer = ByteBuffer.allocate(8192); 16 | 17 | System.out.println("start " + Thread.currentThread()); 18 | try(socket1; socket2) { 19 | for(;;) { 20 | int read = socket1.read(buffer); 21 | System.out.println("read " + read + " from " + Thread.currentThread()); 22 | if (read == -1) { 23 | //socket1.close(); 24 | //socket2.close(); 25 | return; 26 | } 27 | buffer.flip(); 28 | 29 | do { 30 | socket2.write(buffer); 31 | System.out.println("write from " + Thread.currentThread()); 32 | 33 | } while(buffer.hasRemaining()); 34 | 35 | buffer.clear(); 36 | } 37 | } catch (@SuppressWarnings("unused") IOException e) { 38 | throw new UncheckedIOException(e); 39 | } 40 | }; 41 | } 42 | 43 | @SuppressWarnings("resource") 44 | public static void main(String[] args) throws IOException { 45 | var server = ServerSocketChannel.open(); 46 | server.bind(new InetSocketAddress(7777)); 47 | System.out.println("server bound to " + server.getLocalAddress()); 48 | 49 | var remote = SocketChannel.open(); 50 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), Host.PORT)); 51 | //remote.configureBlocking(false); 52 | 53 | System.out.println("accepting ..."); 54 | var client = server.accept(); 55 | //client.configureBlocking(false); 56 | 57 | Thread.ofPlatform().start(runnable(client, remote)); 58 | Thread.ofPlatform().start(runnable(remote, client)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/proxy/TCPVirtualThreadProxy.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.proxy; 2 | 3 | import fr.umlv.loom.executor.UnsafeExecutors; 4 | 5 | import java.io.IOException; 6 | import java.io.UncheckedIOException; 7 | import java.net.InetAddress; 8 | import java.net.InetSocketAddress; 9 | import java.nio.ByteBuffer; 10 | import java.nio.channels.ServerSocketChannel; 11 | import java.nio.channels.SocketChannel; 12 | import java.util.concurrent.Executors; 13 | 14 | public class TCPVirtualThreadProxy { 15 | private static Runnable runnable(SocketChannel socket1, SocketChannel socket2) { 16 | return () -> { 17 | var buffer = ByteBuffer.allocate(8192); 18 | 19 | System.out.println("start " + Thread.currentThread()); 20 | try(socket1; socket2) { 21 | for(;;) { 22 | int read = socket1.read(buffer); 23 | System.out.println("read " + read + " from " + Thread.currentThread()); 24 | if (read == -1) { 25 | //socket1.close(); 26 | //socket2.close(); 27 | return; 28 | } 29 | buffer.flip(); 30 | 31 | do { 32 | socket2.write(buffer); 33 | System.out.println("write from " + Thread.currentThread()); 34 | 35 | } while(buffer.hasRemaining()); 36 | 37 | buffer.clear(); 38 | } 39 | } catch (IOException e) { 40 | throw new UncheckedIOException(e); 41 | } 42 | }; 43 | } 44 | 45 | @SuppressWarnings("resource") 46 | public static void main(String[] args) throws IOException, InterruptedException { 47 | var server = ServerSocketChannel.open(); 48 | server.bind(new InetSocketAddress(7777)); 49 | System.out.println("server bound to " + server.getLocalAddress()); 50 | 51 | var remote = SocketChannel.open(); 52 | remote.connect(new InetSocketAddress(InetAddress.getByName(Host.NAME), Host.PORT)); 53 | //remote.configureBlocking(false); 54 | 55 | System.out.println("accepting ..."); 56 | var client = server.accept(); 57 | //client.configureBlocking(false); 58 | 59 | var executor = UnsafeExecutors.virtualThreadExecutor(Executors.newSingleThreadExecutor()); 60 | executor.execute(runnable(client, remote)); 61 | executor.execute(runnable(remote, client)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/reducer/StructAsyncScopeDemo.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.reducer; 2 | 3 | import fr.umlv.loom.reducer.StructuredAsyncScope.Reducer; 4 | import fr.umlv.loom.reducer.StructuredAsyncScope.Result; 5 | import fr.umlv.loom.reducer.StructuredAsyncScope.Result.State; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | // $JAVA_HOME/bin/java --enable-preview --add-modules jdk.incubator.concurrent ... 12 | public class StructAsyncScopeDemo { 13 | public static void toList() throws InterruptedException { 14 | try(var scope = StructuredAsyncScope.of(Reducer.toList())) { 15 | scope.fork(() -> 3); 16 | scope.fork(() -> { 17 | throw new IOException(); 18 | }); 19 | 20 | List> list = scope.result(); 21 | System.out.println(list); // [Result[state=FAILED, element=null, suppressed=java.io.IOException], Result[state=SUCCEED, element=3, suppressed=null]] 22 | } 23 | } 24 | 25 | public static void max() throws InterruptedException { 26 | try(var scope = StructuredAsyncScope.of(Reducer.max(Integer::compareTo))) { 27 | scope.fork(() -> 3); 28 | scope.fork(() -> { 29 | throw new IOException(); 30 | }); 31 | scope.fork(() -> 42); 32 | 33 | Optional> max = scope.result(); 34 | System.out.println(max); // Optional[Result[state=SUCCEED, element=42, suppressed=java.io.IOException]] 35 | } 36 | } 37 | 38 | public static void first() throws InterruptedException { 39 | try(var scope = StructuredAsyncScope.of(Reducer.first())) { 40 | scope.fork(() -> { 41 | throw new IOException(); 42 | }); 43 | scope.fork(() -> 3); 44 | scope.fork(() -> 42); 45 | 46 | Optional> first = scope.result(); 47 | System.out.println(first); // Optional[Result[state=SUCCEED, element=3, suppressed=java.io.IOException]] 48 | } 49 | } 50 | 51 | public static void firstDropExceptions() throws InterruptedException { 52 | try(var scope = StructuredAsyncScope.of(Reducer.first().dropExceptions())) { 53 | scope.fork(() -> { 54 | throw new IOException(); 55 | }); 56 | scope.fork(() -> 3); 57 | scope.fork(() -> 42); 58 | 59 | Optional> first = scope.result(); 60 | System.out.println(first); // Optional[Result[state=SUCCEED, element=3, suppressed=null]] 61 | } 62 | } 63 | 64 | public static void shutdownOnFailure() throws InterruptedException { 65 | try(var scope = StructuredAsyncScope.of(Reducer.firstException().shutdownOnFailure())) { 66 | scope.fork(() -> null); 67 | /*scope.fork(() -> { 68 | Thread.sleep(50); 69 | throw new IOException(); 70 | });*/ 71 | scope.fork(() -> { 72 | Thread.sleep(100); 73 | return null; 74 | }); 75 | 76 | Optional result = scope.result(); 77 | result.ifPresent(e -> { throw new RuntimeException(e); }); 78 | System.out.println(result); // Optional.empty 79 | } 80 | } 81 | 82 | public static void main(String[] args) throws InterruptedException { 83 | toList(); 84 | max(); 85 | first(); 86 | firstDropExceptions(); 87 | shutdownOnFailure(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/reducer/StructuredAsyncScope.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.reducer; 2 | 3 | import fr.umlv.loom.reducer.StructuredAsyncScope.Result.State; 4 | 5 | import java.lang.invoke.MethodHandles; 6 | import java.lang.invoke.VarHandle; 7 | import java.time.Instant; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.concurrent.StructuredTaskScope; 12 | import java.util.concurrent.TimeoutException; 13 | import java.util.function.Function; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | public final class StructuredAsyncScope extends StructuredTaskScope { 18 | @FunctionalInterface 19 | public interface Combiner { 20 | A apply(A oldValue, Result result, Runnable shouldShutdown); 21 | } 22 | 23 | public record Reducer(Combiner combiner, Function finisher) { 24 | public Reducer { 25 | requireNonNull(combiner); 26 | requireNonNull(finisher); 27 | } 28 | 29 | public Reducer dropExceptions() { 30 | return new Reducer<>((oldValue, result, shouldShutdown) -> combiner.apply(oldValue, new Result<>(result.state, result.element, null), shouldShutdown), finisher); 31 | } 32 | 33 | public static Reducer>> toList() { 34 | record Link (Result result, int size, Link next) { 35 | static Link combine(Link old, Result result, Runnable shutdown) { 36 | return new Link<>(result, old == null ? 1 : old.size + 1, old); 37 | } 38 | static List> finish(Link link) { 39 | if (link == null) { 40 | return List.of(); 41 | } 42 | @SuppressWarnings("unchecked") 43 | var array = (Result[]) new Result[link.size]; 44 | var i = link.size; 45 | for(var l = link; l!= null; l = l.next) { 46 | array[--i] = l.result(); 47 | } 48 | return List.of(array); 49 | } 50 | } 51 | return new Reducer, List>>(Link::combine, Link::finish); 52 | } 53 | 54 | private static Throwable mergeException(Throwable throwable, Throwable other) { 55 | if (throwable == null) { 56 | return other; 57 | } 58 | if (other == null) { 59 | return throwable; 60 | } 61 | throwable.addSuppressed(other); 62 | return throwable; 63 | } 64 | 65 | private static Result maxMergeResult(Result result, Result other, Comparator comparator) { 66 | if (result == null) { 67 | return other; 68 | } 69 | var newException = mergeException(result.suppressed, other.suppressed); 70 | if (result.state == State.SUCCEED) { 71 | if (other.state == State.SUCCEED) { 72 | var e = result.element; 73 | var o = other.element; 74 | var newElement = comparator.compare(e, o) >= 0 ? e : o; 75 | return new Result<>(State.SUCCEED, newElement, newException); 76 | } 77 | return new Result<>(State.SUCCEED, result.element, newException); 78 | } 79 | return new Result<>(other.state, other.element, newException); 80 | } 81 | 82 | public static Reducer>> max(Comparator comparator) { 83 | return new Reducer, Optional>>((r1, r2, shutdown) -> maxMergeResult(r1, r2, comparator), Optional::ofNullable); 84 | } 85 | 86 | private static Result firstMergeResult(Result result, Result other, Runnable shutdown) { 87 | if (result == null) { 88 | if (other.state == State.SUCCEED) { 89 | shutdown.run(); 90 | } 91 | return other; 92 | } 93 | var newException = mergeException(result.suppressed, other.suppressed); 94 | if (result.state == State.SUCCEED) { 95 | return new Result<>(State.SUCCEED, result.element, newException); 96 | } 97 | if (other.state == State.SUCCEED) { 98 | shutdown.run(); 99 | } 100 | return new Result<>(other.state, other.element, newException); 101 | } 102 | 103 | public static Reducer>> first() { 104 | return new Reducer, Optional>>(Reducer::firstMergeResult, Optional::ofNullable); 105 | } 106 | 107 | public Reducer shutdownOnFailure() { 108 | return new Reducer((oldValue, result, shouldShutdown) -> { 109 | if (result.state == State.FAILED) { 110 | shouldShutdown.run(); 111 | } 112 | return combiner.apply(oldValue, result, shouldShutdown); 113 | }, finisher); 114 | } 115 | 116 | public static Reducer> firstException() { 117 | return new Reducer, Optional>((oldValue, result, shouldShutdown) -> { 118 | if (oldValue != null && oldValue.state == State.FAILED) { 119 | return oldValue; 120 | } 121 | return result; 122 | }, result -> Optional.ofNullable(result.suppressed)); 123 | } 124 | } 125 | 126 | public record Result(State state, T element, Throwable suppressed) { 127 | public enum State { 128 | FAILED, SUCCEED 129 | } 130 | 131 | public Result { 132 | requireNonNull(state); 133 | if (state == State.FAILED && element != null) { 134 | throw new IllegalArgumentException("if state == FAILED, element should be null"); 135 | } 136 | } 137 | } 138 | 139 | private static final VarHandle VALUE_HANDLE; 140 | static { 141 | try { 142 | VALUE_HANDLE = MethodHandles.lookup().findVarHandle(StructuredAsyncScope.class, "value", Object.class); 143 | } catch (NoSuchFieldException | IllegalAccessException e) { 144 | throw new AssertionError(e); 145 | } 146 | } 147 | 148 | private volatile A value; 149 | private final Reducer reducer; 150 | 151 | public StructuredAsyncScope(Reducer reducer) { 152 | this.reducer = requireNonNull(reducer); 153 | } 154 | 155 | public static StructuredAsyncScope of(Reducer reducer) { 156 | return new StructuredAsyncScope<>(reducer); 157 | } 158 | 159 | @Override 160 | protected void handleComplete(Subtask subtask) { 161 | Result result = switch (subtask.state()) { 162 | case UNAVAILABLE -> throw new AssertionError(); 163 | case SUCCESS -> result = new Result(State.SUCCEED, subtask.get(), null); 164 | case FAILED -> result = new Result(State.FAILED, null, subtask.exception()); 165 | }; 166 | var shouldShutdown = new Runnable() { 167 | private boolean shutdown; 168 | @Override 169 | public void run() { 170 | shutdown = true; 171 | } 172 | }; 173 | for(;;) { 174 | var oldValue = value; // volatile read 175 | var newValue = reducer.combiner.apply(oldValue, result, shouldShutdown); 176 | if (VALUE_HANDLE.compareAndSet(this, oldValue, newValue)) { // volatile read/write 177 | if (shouldShutdown.shutdown) { 178 | shutdown(); 179 | } 180 | return; 181 | } 182 | } 183 | } 184 | 185 | public V result() throws InterruptedException { 186 | join(); 187 | return reducer.finisher.apply(value); // volatile read 188 | } 189 | 190 | public V result(Instant deadline) throws InterruptedException, TimeoutException { 191 | joinUntil(deadline); 192 | return reducer.finisher.apply(value); // volatile read 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/structured/Invokable.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.structured; 2 | 3 | /** 4 | * A callable that propagates the checked exceptions 5 | * 6 | * @param type of the result 7 | * @param type of the checked exception, uses {@code RuntimeException} otherwise. 8 | */ 9 | @FunctionalInterface 10 | public interface Invokable { 11 | /** 12 | * Compute the computation. 13 | * 14 | * @return a result 15 | * @throws E an exception 16 | * @throws InterruptedException if the computation is interrupted or cancelled 17 | */ 18 | T invoke() throws E, InterruptedException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/structured/StructuredScopeShutdownOnFailure.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.structured; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.StructuredTaskScope; 5 | import java.util.function.Function; 6 | import java.util.function.Supplier; 7 | 8 | public class StructuredScopeShutdownOnFailure implements AutoCloseable { 9 | private final StructuredTaskScope.ShutdownOnFailure scope; 10 | 11 | public StructuredScopeShutdownOnFailure() { 12 | this.scope = new StructuredTaskScope.ShutdownOnFailure(); 13 | } 14 | 15 | public Supplier fork(Invokable invokable) { 16 | var subtask = scope.fork(invokable::invoke); 17 | return () -> switch (subtask.state()) { 18 | case UNAVAILABLE, FAILED -> throw new IllegalStateException(); 19 | case SUCCESS -> subtask.get(); 20 | }; 21 | } 22 | 23 | public void joinAll() throws E, InterruptedException { 24 | joinAll(e -> e); 25 | } 26 | 27 | public void joinAll(Function exceptionMapper) throws X, InterruptedException { 28 | Objects.requireNonNull(exceptionMapper, "exceptionMapper is null"); 29 | scope.join(); 30 | scope.throwIfFailed(throwable -> { 31 | if (throwable instanceof RuntimeException e) { 32 | throw e; 33 | } 34 | if (throwable instanceof Error e) { 35 | throw e; 36 | } 37 | if (throwable instanceof InterruptedException e) { 38 | return (X) e; // dubious cast 39 | } 40 | return exceptionMapper.apply((E) throwable); 41 | }); 42 | } 43 | 44 | @Override 45 | public void close() { 46 | scope.close(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/structured/StructuredScopeShutdownOnSuccess.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.structured; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.StructuredTaskScope; 5 | import java.util.function.Function; 6 | 7 | public class StructuredScopeShutdownOnSuccess implements AutoCloseable { 8 | private final StructuredTaskScope.ShutdownOnSuccess scope; 9 | 10 | public StructuredScopeShutdownOnSuccess() { 11 | this.scope = new StructuredTaskScope.ShutdownOnSuccess(); 12 | } 13 | 14 | public void fork(Invokable invokable) { 15 | scope.fork(invokable::invoke); 16 | } 17 | 18 | public T joinAll() throws E, InterruptedException { 19 | return joinAll(e -> e); 20 | } 21 | 22 | public T joinAll(Function exceptionMapper) throws X, InterruptedException { 23 | Objects.requireNonNull(exceptionMapper, "exceptionMapper is null"); 24 | scope.join(); 25 | return scope.result(throwable -> { 26 | if (throwable instanceof RuntimeException e) { 27 | throw e; 28 | } 29 | if (throwable instanceof Error e) { 30 | throw e; 31 | } 32 | if (throwable instanceof InterruptedException e) { 33 | return (X) e; // dubious cast 34 | } 35 | return exceptionMapper.apply((E) throwable); 36 | }); 37 | } 38 | 39 | @Override 40 | public void close() { 41 | scope.close(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/fr/umlv/loom/tutorial/RickAndMortyExample.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.tutorial; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import fr.umlv.loom.structured.StructuredScopeAsStream; 6 | 7 | import java.io.IOException; 8 | import java.io.UncheckedIOException; 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | import java.net.http.HttpClient; 12 | import java.net.http.HttpRequest; 13 | import java.net.http.HttpResponse; 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | import java.util.concurrent.*; 17 | import java.util.stream.Collectors; 18 | 19 | import static java.util.stream.Collectors.toSet; 20 | 21 | public class RickAndMortyExample { 22 | @JsonIgnoreProperties(ignoreUnknown = true) 23 | record Episode(String name, Set characters) {} 24 | 25 | @JsonIgnoreProperties(ignoreUnknown = true) 26 | record Character(String name) {} 27 | 28 | private static Set characterOfEpisode(int episodeId) throws IOException, InterruptedException { 29 | try(var httpClient = HttpClient.newHttpClient()) { 30 | var request = HttpRequest.newBuilder() 31 | .uri(URI.create("https://rickandmortyapi.com/api/episode/" + episodeId)) 32 | .GET() 33 | .build(); 34 | 35 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); 36 | 37 | var objectMapper = new ObjectMapper(); 38 | var episode = objectMapper.readValue(response.body(), Episode.class); 39 | return episode.characters(); 40 | } 41 | } 42 | 43 | private static CompletableFuture> characterOfEpisodeFuture(int episodeId) { 44 | try (var httpClient = HttpClient.newHttpClient()) { 45 | var request = HttpRequest.newBuilder() 46 | .uri(URI.create("https://rickandmortyapi.com/api/episode/" + episodeId)) 47 | .GET() 48 | .build(); 49 | 50 | return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) 51 | .thenCompose(response -> { 52 | var objectMapper = new ObjectMapper(); 53 | Episode episode; 54 | try { 55 | episode = objectMapper.readValue(response.body(), Episode.class); 56 | } catch (IOException e) { 57 | return CompletableFuture.failedFuture(e); 58 | } 59 | return CompletableFuture.completedFuture(episode.characters()); 60 | }); 61 | } 62 | } 63 | 64 | private static Character character(URI uri) throws IOException, InterruptedException { 65 | try(var httpClient = HttpClient.newHttpClient()) { 66 | var request = HttpRequest.newBuilder() 67 | .uri(uri) 68 | .GET() 69 | .build(); 70 | 71 | var response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); 72 | 73 | var objectMapper = new ObjectMapper(); 74 | return objectMapper.readValue(response.body(), Character.class); 75 | } 76 | } 77 | 78 | private static void time(Callable callable) { 79 | Object result; 80 | var start = System.currentTimeMillis(); 81 | try { 82 | result = callable.call(); 83 | } catch(Exception e) { 84 | throw new AssertionError(e); 85 | } finally { 86 | var end = System.currentTimeMillis(); 87 | System.err.println("time: " + (end - start) + " ms"); 88 | } 89 | System.out.println(result); 90 | } 91 | 92 | public static Set synchronous() throws IOException, InterruptedException { 93 | var character1 = characterOfEpisode(1).stream() 94 | .map(uri -> { 95 | try { 96 | return character(uri); 97 | } catch (IOException|InterruptedException e) { 98 | throw new RuntimeException(e); 99 | } 100 | }) 101 | .collect(toSet()); 102 | var character2 = characterOfEpisode(2).stream() 103 | .map(uri -> { 104 | try { 105 | return character(uri); 106 | } catch (IOException|InterruptedException e) { 107 | throw new RuntimeException(e); 108 | } 109 | }) 110 | .collect(toSet()); 111 | var commonCharacters = new HashSet<>(character1); 112 | commonCharacters.retainAll(character2); 113 | return commonCharacters; 114 | } 115 | 116 | public static Set synchronous2() throws IOException, InterruptedException { 117 | var characterURIs1 = characterOfEpisode(1); 118 | var characterURIs2 = characterOfEpisode(2); 119 | var commonCharacterUris = new HashSet<>(characterURIs1); 120 | commonCharacterUris.retainAll(characterURIs2); 121 | return commonCharacterUris.stream() 122 | .map(uri -> { 123 | try { 124 | return character(uri); 125 | } catch (IOException|InterruptedException e) { 126 | throw new RuntimeException(e); 127 | } 128 | }) 129 | .collect(toSet()); 130 | } 131 | 132 | public static Set executors() throws IOException, InterruptedException, ExecutionException { 133 | Set characterURIs1, characterURIs2; 134 | try(var executor = Executors.newVirtualThreadPerTaskExecutor()) { 135 | var future1 = executor.submit(() -> characterOfEpisode(1)); 136 | var future2 = executor.submit(() -> characterOfEpisode(2)); 137 | characterURIs1 = future1.get(); 138 | characterURIs2 = future2.get(); 139 | } 140 | var commonCharacterURIs = new HashSet<>(characterURIs1); 141 | commonCharacterURIs.retainAll(characterURIs2); 142 | try(var executor = Executors.newVirtualThreadPerTaskExecutor()) { 143 | var tasks = commonCharacterURIs.stream() 144 | .>map(uri -> () -> character(uri)) 145 | .toList(); 146 | var futures = executor.invokeAll(tasks); 147 | return futures.stream(). 148 | map(future -> { 149 | try { 150 | return future.get(); 151 | } catch (InterruptedException | ExecutionException e) { 152 | throw new RuntimeException(e); 153 | } 154 | }) 155 | .collect(toSet()); 156 | } 157 | } 158 | 159 | public static Set sts() throws InterruptedException { 160 | Set characterURIs1, characterURIs2; 161 | try(var scope = new StructuredTaskScope.ShutdownOnFailure()) { 162 | var future1 = scope.fork(() -> characterOfEpisode(1)); 163 | var future2 = scope.fork(() -> characterOfEpisode(2)); 164 | scope.join(); 165 | characterURIs1 = future1.get(); 166 | characterURIs2 = future2.get(); 167 | } 168 | var commonCharacterURIs = new HashSet<>(characterURIs1); 169 | commonCharacterURIs.retainAll(characterURIs2); 170 | try(var scope = new StructuredTaskScope.ShutdownOnFailure()) { 171 | var futures = commonCharacterURIs.stream() 172 | .map(characterURI -> scope.fork(() -> character(characterURI))) 173 | .toList(); 174 | scope.join(); 175 | return futures.stream() 176 | .map(StructuredTaskScope.Subtask::get) 177 | .collect(toSet()); 178 | } 179 | } 180 | 181 | public static Set asyncScope() throws IOException, InterruptedException { 182 | Set characterURIs1, characterURIs2; 183 | try(var scope = new StructuredScopeAsStream, IOException>()) { 184 | var task1 = scope.fork(() -> characterOfEpisode(1)); 185 | var task2 = scope.fork(() -> characterOfEpisode(2)); 186 | var errorOpt = scope.joinAll(stream -> stream.filter(StructuredScopeAsStream.Result::isFailed).findFirst()); 187 | if (errorOpt.isPresent()) { 188 | throw errorOpt.orElseThrow().failure(); 189 | } 190 | characterURIs1 = task1.get(); 191 | characterURIs2 = task2.get(); 192 | } 193 | var commonCharacterURIs = new HashSet<>(characterURIs1); 194 | commonCharacterURIs.retainAll(characterURIs2); 195 | try(var scope = new StructuredScopeAsStream()) { 196 | for(var characterURI: commonCharacterURIs) { 197 | scope.fork(() -> character(characterURI)); 198 | } 199 | return scope.joinAll(stream -> stream 200 | .peek(r -> { 201 | if (r.isFailed()) { 202 | throw new UncheckedIOException(r.failure()); 203 | } 204 | }) 205 | .map(StructuredScopeAsStream.Result::result) 206 | .collect(Collectors.toSet())); 207 | } 208 | } 209 | 210 | public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException { 211 | time(() -> synchronous()); 212 | time(() -> synchronous2()); 213 | time(() -> executors()); 214 | time(() -> sts()); 215 | time(() -> asyncScope()); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/continuation/ContinuationTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.continuation; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | public class ContinuationTest { 8 | 9 | @Test 10 | public void startAndYield() { 11 | var builder = new StringBuilder(); 12 | var continuation = new Continuation(() -> { 13 | builder.append("continuation -- start\n"); 14 | Continuation.yield(); 15 | builder.append("continuation -- middle\n"); 16 | Continuation.yield(); 17 | builder.append("continuation -- end\n"); 18 | }); 19 | builder.append("main -- before start\n"); 20 | continuation.run(); 21 | builder.append("main -- after start\n"); 22 | builder.append("main -- before start 2\n"); 23 | continuation.run(); 24 | builder.append("main -- after start 2\n"); 25 | builder.append("main -- before start 3\n"); 26 | continuation.run(); 27 | builder.append("main -- after start 3\n"); 28 | assertEquals(""" 29 | main -- before start 30 | continuation -- start 31 | main -- after start 32 | main -- before start 2 33 | continuation -- middle 34 | main -- after start 2 35 | main -- before start 3 36 | continuation -- end 37 | main -- after start 3 38 | """, builder.toString()); 39 | } 40 | 41 | private static String carrierThreadName() { 42 | var name = Thread.currentThread().toString(); 43 | var index = name.lastIndexOf('@'); 44 | if (index == -1) { 45 | throw new AssertionError(); 46 | } 47 | return name.substring(index + 1); 48 | } 49 | 50 | @Test 51 | public void startWhenDone() { 52 | var continuation = new Continuation(() -> {}); 53 | continuation.run(); 54 | assertThrows(IllegalStateException.class, continuation::run); 55 | } 56 | 57 | @Test 58 | public void yieldNotBound() { 59 | assertThrows(IllegalStateException.class, Continuation::yield); 60 | } 61 | } -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/executor/UnsafeExecutorsTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.executor; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.concurrent.CopyOnWriteArraySet; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ForkJoinPool; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | public class UnsafeExecutorsTest { 13 | private static String carrierThreadName() { 14 | var name = Thread.currentThread().toString(); 15 | var index = name.lastIndexOf('@'); 16 | if (index == -1) { 17 | throw new AssertionError(); 18 | } 19 | return name.substring(index + 1); 20 | } 21 | 22 | @Test 23 | public void virtualThreadExecutorSingleThreadExecutor() throws InterruptedException { 24 | var executor = Executors.newSingleThreadExecutor(); 25 | var virtualExecutor = UnsafeExecutors.virtualThreadExecutor(executor); 26 | var carrierThreadNames = new CopyOnWriteArraySet(); 27 | for(var i = 0; i < 10; i++) { 28 | virtualExecutor.execute(() -> carrierThreadNames.add(carrierThreadName())); 29 | } 30 | executor.shutdown(); 31 | executor.awaitTermination(1, TimeUnit.DAYS); 32 | assertEquals(1, carrierThreadNames.size()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/executor/VirtualThreadExecutorTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.executor; 2 | 3 | /* 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Future; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.TimeoutException; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | public class VirtualThreadExecutorTest { 17 | 18 | @Test 19 | public void shutdown() throws ExecutionException, InterruptedException { 20 | var executor = new VirtualThreadExecutor(); 21 | var future = executor.submit(() -> { 22 | Thread.sleep(100); 23 | return "hello"; 24 | }); 25 | executor.shutdown(); 26 | assertEquals("hello", future.get()); 27 | } 28 | 29 | @Test 30 | public void shutdownNow() throws InterruptedException { 31 | var executor = new VirtualThreadExecutor(); 32 | var future = executor.submit(() -> { 33 | Thread.sleep(1_000); 34 | return "hello"; 35 | }); 36 | executor.shutdownNow(); 37 | try { 38 | future.get(); 39 | fail(); 40 | } catch (ExecutionException e) { 41 | assertTrue(e.getCause() instanceof InterruptedException); 42 | } 43 | } 44 | 45 | @Test 46 | public void isShutdown() { 47 | var executor = new VirtualThreadExecutor(); 48 | executor.shutdown(); 49 | assertTrue(executor.isShutdown()); 50 | } 51 | 52 | @Test 53 | public void isTerminated() { 54 | var executor = new VirtualThreadExecutor(); 55 | executor.awaitTermination(1, TimeUnit.SECONDS); 56 | assertTrue(executor.isTerminated()); 57 | } 58 | 59 | @Test 60 | public void awaitTermination() { 61 | var executor = new VirtualThreadExecutor(); 62 | var future = executor.submit(() -> { 63 | Thread.sleep(1); 64 | return "hello"; 65 | }); 66 | executor.awaitTermination(1, TimeUnit.SECONDS); 67 | assertEquals("hello", future.resultNow()); 68 | } 69 | 70 | @Test 71 | public void execute() { 72 | var executor = new VirtualThreadExecutor(); 73 | var result = new AtomicBoolean(); 74 | executor.execute(() -> result.set(true)); 75 | executor.awaitTermination(1, TimeUnit.SECONDS); 76 | assertTrue(result.get()); 77 | } 78 | 79 | @Test 80 | public void submitCallableResult() throws ExecutionException, InterruptedException { 81 | var executor = new VirtualThreadExecutor(); 82 | var future = executor.submit(() -> { 83 | Thread.sleep(50); 84 | return 42; 85 | }); 86 | executor.shutdown(); 87 | assertEquals(42, (int) future.get()); 88 | } 89 | @Test 90 | public void submitCallableException() throws InterruptedException { 91 | var executor = new VirtualThreadExecutor(); 92 | var future = executor.submit(() -> { 93 | Thread.sleep(50); 94 | throw new RuntimeException("oops"); 95 | }); 96 | executor.shutdown(); 97 | try { 98 | future.get(); 99 | fail(); 100 | } catch(ExecutionException e) { 101 | assertTrue(e.getCause() instanceof RuntimeException); 102 | } 103 | } 104 | 105 | @Test 106 | public void testSubmitRunnable() throws ExecutionException, InterruptedException { 107 | var executor = new VirtualThreadExecutor(); 108 | var result = new AtomicBoolean(); 109 | var future = executor.submit((Runnable) () -> result.set(true)); 110 | executor.shutdown(); 111 | assertNull(future.get()); 112 | assertTrue(result.get()); 113 | } 114 | 115 | @Test 116 | public void testSubmitRunnableWithValue() throws ExecutionException, InterruptedException { 117 | var executor = new VirtualThreadExecutor(); 118 | var result = new AtomicBoolean(); 119 | var future = executor.submit((Runnable) () -> result.set(true), 42); 120 | executor.shutdown(); 121 | assertEquals(42, (int) future.get()); 122 | assertTrue(result.get()); 123 | } 124 | 125 | @Test 126 | public void invokeAll() throws InterruptedException { 127 | var executor = new VirtualThreadExecutor(); 128 | var tasks = List.>of( 129 | () -> { 130 | Thread.sleep(1_000); 131 | return 1; 132 | }, 133 | () -> { 134 | Thread.sleep(1_000); 135 | return 101; 136 | }); 137 | var list = executor.invokeAll(tasks); 138 | executor.shutdown(); 139 | assertEquals(List.of(1, 101), list.stream().map(Future::resultNow).toList()); 140 | } 141 | 142 | @Test 143 | public void invokeAllWithTimeout() throws InterruptedException { 144 | var executor = new VirtualThreadExecutor(); 145 | var tasks = List.>of( 146 | () -> 1, 147 | () -> 101 148 | ); 149 | var list = executor.invokeAll(tasks, 1, TimeUnit.SECONDS); 150 | executor.shutdown(); 151 | assertEquals(List.of(1, 101), list.stream().map(Future::resultNow).toList()); 152 | } 153 | @Test 154 | public void invokeAllWithTimeoutDeadlineReached() throws InterruptedException { 155 | var executor = new VirtualThreadExecutor(); 156 | var tasks = List.>of( 157 | () -> { 158 | Thread.sleep(1_000); 159 | return 1; 160 | }, 161 | () -> { 162 | return 101; 163 | }); 164 | var list = executor.invokeAll(tasks, 500, TimeUnit.MILLISECONDS); 165 | executor.shutdown(); 166 | assertEquals(List.of(101), list.stream().map(Future::resultNow).toList()); 167 | } 168 | 169 | @Test 170 | public void invokeAny() throws ExecutionException, InterruptedException, TimeoutException { 171 | var executor = new VirtualThreadExecutor(); 172 | var tasks = List.>of( 173 | () -> { 174 | return 42; 175 | }, 176 | () -> { 177 | Thread.sleep(1_000); 178 | return 101; 179 | }); 180 | var result = executor.invokeAny(tasks); 181 | executor.shutdown(); 182 | assertEquals(42, (int) result); 183 | } 184 | 185 | @Test 186 | public void invokeAnyWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { 187 | var executor = new VirtualThreadExecutor(); 188 | var tasks = List.>of( 189 | () -> { 190 | Thread.sleep(50); 191 | return 42; 192 | }, 193 | () -> { 194 | return 101; 195 | }); 196 | var result = executor.invokeAny(tasks, 1_000, TimeUnit.MILLISECONDS); 197 | executor.shutdown(); 198 | assertEquals(101, (int) result); 199 | } 200 | @Test 201 | public void invokeAnyWithTimeoutDeadlineReached() throws ExecutionException, InterruptedException, TimeoutException { 202 | var executor = new VirtualThreadExecutor(); 203 | var tasks = List.>of( 204 | () -> { 205 | Thread.sleep(1_000); 206 | return 42; 207 | }, 208 | () -> { 209 | Thread.sleep(1_000); 210 | return 101; 211 | }); 212 | assertThrows(TimeoutException.class, () -> executor.invokeAny(tasks, 50, TimeUnit.MILLISECONDS)); 213 | executor.shutdown(); 214 | } 215 | } 216 | */ -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/oldstructured/AsyncScope2ExampleTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.oldstructured; 2 | 3 | import fr.umlv.loom.oldstructured.AsyncScope2; 4 | import fr.umlv.loom.oldstructured.AsyncScope2.DeadlineException; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.IOException; 8 | import java.time.Instant; 9 | import java.time.temporal.ChronoUnit; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.Random; 14 | import java.util.stream.Stream; 15 | 16 | public final class AsyncScope2ExampleTest { 17 | sealed interface TravelItem permits Weather, Quotation {} 18 | record Weather(String source, String text) implements TravelItem { } 19 | record Quotation(String source, int price) implements TravelItem {} 20 | 21 | private static final Weather DEFAULT_WEATHER = new Weather("", "Sunny"); 22 | 23 | private Quotation quotation() throws InterruptedException { 24 | var random = new Random(); 25 | try (var quotationScope = AsyncScope2.unordered()) { 26 | quotationScope.async(() -> { 27 | Thread.sleep(random.nextInt(20, 120)); 28 | return new Quotation("QA", random.nextInt(50, 100)); 29 | }); 30 | quotationScope.async(() -> { 31 | Thread.sleep(random.nextInt(30, 130)); 32 | //return new Quotation("QB", random.nextInt(30, 100)); 33 | throw new IOException("oops !"); 34 | }); 35 | 36 | return quotationScope 37 | .recover(e -> null) // ignore 38 | .await(s -> s.filter(Objects::nonNull).min(Comparator.comparing(Quotation::price))).orElseThrow(); 39 | } 40 | } 41 | 42 | private Weather weather() throws InterruptedException { 43 | var random = new Random(); 44 | try (var weatherScope = AsyncScope2.unordered()) { 45 | weatherScope.async(() -> { 46 | Thread.sleep(random.nextInt(50, 100)); 47 | return new Weather("WA", "Cloudy"); 48 | }); 49 | weatherScope.async(() -> { 50 | Thread.sleep(random.nextInt(60, 80)); 51 | return new Weather("WB", "Sunny"); 52 | }); 53 | return weatherScope 54 | .deadline(Instant.now().plus(70, ChronoUnit.MILLIS)) 55 | .await(Stream::findFirst).orElse(DEFAULT_WEATHER); 56 | } catch (DeadlineException e) { 57 | return DEFAULT_WEATHER; 58 | } 59 | } 60 | 61 | @Test 62 | public void travelAgency() throws InterruptedException { 63 | List travelItems; 64 | try(var travelScope = AsyncScope2.unordered()) { 65 | travelScope.async(this::quotation); 66 | travelScope.async(this::weather); 67 | 68 | travelItems = travelScope.await(Stream::toList); 69 | } 70 | 71 | Quotation quotation = null; 72 | Weather weather = null; 73 | for(var travelItem: travelItems) { 74 | //switch (travelItem) { 75 | // case Quotation q -> quotation = q; 76 | // case Weather w -> weather = w; 77 | //} 78 | if (travelItem instanceof Quotation q) { 79 | quotation = q; 80 | } else if (travelItem instanceof Weather w) { 81 | weather = w; 82 | } else throw new AssertionError(); 83 | } 84 | System.out.println("quotation " + quotation + " weather " + weather); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/oldstructured/AsyncScope2Test.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.oldstructured; 2 | 3 | import fr.umlv.loom.oldstructured.AsyncScope2; 4 | import fr.umlv.loom.oldstructured.AsyncScope2.DeadlineException; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.IOException; 8 | import java.io.UncheckedIOException; 9 | import java.time.Instant; 10 | import java.time.temporal.ChronoUnit; 11 | import java.util.List; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | import java.util.stream.Stream; 14 | 15 | import static java.util.stream.IntStream.range; 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | import static org.junit.jupiter.api.Assertions.assertNull; 18 | import static org.junit.jupiter.api.Assertions.assertThrows; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | import static org.junit.jupiter.api.Assertions.fail; 21 | 22 | public final class AsyncScope2Test { 23 | @Test 24 | public void ordered() throws InterruptedException { 25 | try(var scope = AsyncScope2.ordered()) { 26 | scope.async(() -> 42); 27 | assertEquals(List.of(42), scope.await(Stream::toList)); 28 | } 29 | } 30 | 31 | @Test 32 | public void orderedWithSleep() throws InterruptedException { 33 | try(var scope = AsyncScope2.ordered()) { 34 | scope.async(() -> { 35 | Thread.sleep(200); 36 | return 10; 37 | }); 38 | scope.async(() -> 20); 39 | assertEquals(List.of(10, 20), scope.await(Stream::toList)); 40 | } 41 | } 42 | 43 | @Test 44 | public void orderedSimple() throws InterruptedException { 45 | try(var scope = AsyncScope2.ordered()) { 46 | scope.async(() -> 10); 47 | scope.async(() -> 20); 48 | assertEquals(List.of(10, 20), scope.await(Stream::toList)); 49 | } 50 | } 51 | 52 | @Test 53 | public void orderedSeveralTasks() throws InterruptedException { 54 | try(var scope = AsyncScope2.ordered()) { 55 | range(0, 10_000).forEach(i -> scope.async(() -> i)); 56 | assertEquals(49_995_000, (int) scope.await(stream -> stream.mapToInt(v -> v).sum())); 57 | } 58 | } 59 | 60 | @Test 61 | public void orderedEmpty() throws InterruptedException { 62 | try(var scope = AsyncScope2.ordered()) { 63 | scope.await(stream -> stream.peek(__ -> fail()).findFirst()); 64 | } 65 | } 66 | 67 | @Test 68 | public void unordered() throws InterruptedException { 69 | try(var scope = AsyncScope2.unordered()) { 70 | scope.async(() -> { 71 | Thread.sleep(500); 72 | return 500; 73 | }); 74 | scope.async(() -> { 75 | Thread.sleep(1); 76 | return 1; 77 | }); 78 | assertEquals(List.of(1, 500), scope.await(Stream::toList)); 79 | } 80 | } 81 | 82 | @Test 83 | public void unorderedSimple() throws InterruptedException { 84 | try(var scope = AsyncScope2.unordered()) { 85 | scope.async(() -> { 86 | Thread.sleep(200); 87 | return 10; 88 | }); 89 | scope.async(() -> 20); 90 | assertEquals(List.of(20, 10), scope.await(Stream::toList)); 91 | } 92 | } 93 | 94 | @Test 95 | public void unorderedFindFirst() throws InterruptedException { 96 | try(var scope = AsyncScope2.unordered()) { 97 | scope.async(() -> { 98 | Thread.sleep(200); 99 | return 10; 100 | }); 101 | scope.async(() -> 20); 102 | assertEquals(20, scope.await(Stream::findFirst).orElseThrow()); 103 | } 104 | } 105 | 106 | @Test 107 | public void unorderedShortcut() throws InterruptedException { 108 | var box = new AtomicBoolean(); 109 | try(var scope = AsyncScope2.unordered()) { 110 | scope.async(() -> { 111 | try { 112 | Thread.sleep(1_000); 113 | } catch (InterruptedException e) { 114 | box.set(true); 115 | throw e; 116 | } 117 | throw new AssertionError("fail !"); 118 | }); 119 | scope.async(() -> { 120 | Thread.sleep(1); 121 | return 1; 122 | }); 123 | assertEquals(1, scope.await(Stream::findFirst).orElseThrow()); 124 | } 125 | assertTrue(box.get()); 126 | } 127 | 128 | @Test 129 | public void unorderedEmpty() throws InterruptedException { 130 | try(var scope = AsyncScope2.unordered()) { 131 | scope.await(stream -> stream.peek(__ -> fail()).findFirst()); 132 | } 133 | } 134 | 135 | @Test 136 | public void asyncCalledAfterResult() throws InterruptedException { 137 | try(var scope = AsyncScope2.ordered()) { 138 | scope.await(__ -> null); 139 | assertThrows(IllegalStateException.class, () -> scope.async(() -> null)); 140 | } 141 | } 142 | 143 | @Test 144 | public void recoverAndWrapException() { 145 | try(var scope = AsyncScope2.ordered()) { 146 | scope.async(() -> { 147 | throw new IOException("boom !"); 148 | }); 149 | assertThrows(UncheckedIOException.class, () -> scope 150 | .recover(exception -> { throw new UncheckedIOException(exception); }) 151 | .await(Stream::toList)); 152 | } 153 | } 154 | 155 | @Test 156 | public void recoverWithAValue() throws InterruptedException { 157 | try(var scope = AsyncScope2.ordered()) { 158 | scope.async(() -> { 159 | throw new IOException("boom !"); 160 | }); 161 | scope.async(() -> 1); 162 | assertEquals(42, (int) scope 163 | .recover((IOException exception) -> 41) 164 | .await(stream -> stream.mapToInt(v -> v).sum())); 165 | } 166 | } 167 | 168 | @Test 169 | public void recoverCanNotRecoverRuntimeExceptions() { 170 | try(var scope = AsyncScope2.ordered()) { 171 | scope.async(() -> { 172 | throw new RuntimeException("boom !"); 173 | }); 174 | assertThrows(RuntimeException.class, () -> scope 175 | .recover(exception -> fail()) // should not be called 176 | .await(stream -> stream.peek(__ -> fail()).findFirst())); 177 | } 178 | } 179 | 180 | @Test 181 | public void recoverPrecondition() { 182 | try(var scope = AsyncScope2.ordered()) { 183 | assertThrows(NullPointerException.class, () -> scope.recover(null)); 184 | } 185 | } 186 | 187 | @Test 188 | public void recoverSpecifiedTwice() { 189 | try(var scope = AsyncScope2.ordered()) { 190 | assertThrows(IllegalStateException.class, () -> scope 191 | .recover(exception -> null) 192 | .recover(exception -> null)); 193 | } 194 | } 195 | 196 | @Test 197 | public void recoverCalledAfterAwait() throws Exception { 198 | try(var scope = AsyncScope2.ordered()) { 199 | scope.await(__ -> null); 200 | assertThrows(IllegalStateException.class, () -> scope.recover(__ -> null)); 201 | } 202 | } 203 | 204 | @Test 205 | public void deadline() { 206 | try(var scope = AsyncScope2.unordered()) { 207 | scope.async(() -> { 208 | Thread.sleep(5_000); 209 | throw new AssertionError("fail !"); 210 | }); 211 | assertThrows(DeadlineException.class, () -> scope 212 | .deadline(Instant.now().plus(100, ChronoUnit.MILLIS)) 213 | .await(stream -> stream.peek(__ -> fail()).toList())); 214 | } 215 | } 216 | 217 | @Test 218 | public void deadlineLongDeadline() throws InterruptedException { 219 | try(var scope = AsyncScope2.unordered()) { 220 | scope.async(() -> { 221 | Thread.sleep(1); 222 | return 1; 223 | }); 224 | assertEquals(1, scope 225 | .deadline(Instant.now().plus(1_000, ChronoUnit.MILLIS)) 226 | .await(Stream::findFirst).orElseThrow()); 227 | } 228 | } 229 | @Test 230 | public void deadlineUnordered() { 231 | try(var scope = AsyncScope2.unordered()) { 232 | scope.async(() -> { 233 | Thread.sleep(5_000); 234 | throw new AssertionError("fail !"); 235 | }); 236 | assertThrows(DeadlineException.class, () -> scope 237 | .deadline(Instant.now().plus(100, ChronoUnit.MILLIS)) 238 | .await(stream -> stream.peek(__ -> fail()).toList())); 239 | } 240 | } 241 | 242 | @Test 243 | public void deadlineUnorderedLongDeadline() throws InterruptedException { 244 | try(var scope = AsyncScope2.unordered()) { 245 | scope.async(() -> { 246 | Thread.sleep(1); 247 | return 1; 248 | }); 249 | assertEquals(1,scope 250 | .deadline(Instant.now().plus(1_000, ChronoUnit.MILLIS)) 251 | .await(Stream::findFirst).orElseThrow()); 252 | } 253 | } 254 | 255 | @Test 256 | public void deadlinePrecondition() { 257 | try(var scope = AsyncScope2.ordered()) { 258 | assertThrows(NullPointerException.class, () -> scope.deadline(null)); 259 | } 260 | } 261 | 262 | @Test 263 | public void deadlineSpecifiedTwice() { 264 | try(var scope = AsyncScope2.ordered()) { 265 | assertThrows(IllegalStateException.class, () -> scope 266 | .deadline(Instant.now()) 267 | .deadline(Instant.now())); 268 | } 269 | } 270 | 271 | @Test 272 | public void deadlineCalledAfterResult() throws InterruptedException { 273 | try(var scope = AsyncScope2.ordered()) { 274 | scope.await(__ -> null); 275 | assertThrows(IllegalStateException.class, () -> scope.deadline(Instant.now())); 276 | } 277 | } 278 | 279 | @Test 280 | public void await() throws InterruptedException { 281 | try(var scope = AsyncScope2.ordered()) { 282 | scope.async(() -> { 283 | Thread.sleep(1); 284 | return 1; 285 | }); 286 | assertEquals(List.of(1), scope.await(Stream::toList)); 287 | } 288 | } 289 | 290 | @Test 291 | public void awaitWithNullResult() throws InterruptedException { 292 | try(var scope = AsyncScope2.ordered()) { 293 | scope.async(() -> null); 294 | assertNull(scope.await(Stream::toList).get(0)); 295 | } 296 | } 297 | 298 | @Test 299 | public void awaitShortcut() throws InterruptedException { 300 | var box = new AtomicBoolean(); 301 | try(var scope = AsyncScope2.ordered()) { 302 | scope.async(() -> { 303 | Thread.sleep(1); 304 | return 1; 305 | }); 306 | scope.async(() -> { 307 | try { 308 | Thread.sleep(1_000); 309 | } catch (InterruptedException e) { 310 | box.set(true); 311 | throw e; 312 | } 313 | throw new AssertionError("fail !"); 314 | }); 315 | assertEquals(1, scope.await(Stream::findFirst).orElseThrow()); 316 | } 317 | assertTrue(box.get()); 318 | } 319 | 320 | @Test 321 | public void awaitPrecondition() { 322 | try(var scope = AsyncScope2.ordered()) { 323 | assertThrows(NullPointerException.class, () -> scope.await(null)); 324 | } 325 | } 326 | 327 | @Test 328 | public void awaitCalledTwice() throws InterruptedException { 329 | try(var scope = AsyncScope2.ordered()) { 330 | scope.await(__ -> null); 331 | assertThrows(IllegalStateException.class, () -> scope.await(__ -> null)); 332 | } 333 | } 334 | 335 | @Test 336 | public void close() { 337 | var box = new AtomicBoolean(); 338 | try(var scope = AsyncScope2.ordered()) { 339 | scope.async(() -> { 340 | try { 341 | Thread.sleep(1_000); 342 | } catch (InterruptedException e) { 343 | box.set(true); 344 | throw e; 345 | } 346 | throw new AssertionError("fail !"); 347 | }); 348 | 349 | // do nothing 350 | } 351 | assertTrue(box.get()); 352 | } 353 | 354 | @Test 355 | public void closeCalledTwice() { 356 | var scope = AsyncScope2.ordered(); 357 | scope.close(); 358 | scope.close(); 359 | } 360 | 361 | @Test 362 | public void fullExample() throws InterruptedException { 363 | List list; 364 | try(var scope = AsyncScope2.unordered()) { 365 | scope.async(() -> { 366 | Thread.sleep(400); 367 | return 111; 368 | }); 369 | scope.async(() -> { 370 | Thread.sleep(200); 371 | throw new IOException("boom !"); 372 | }); 373 | scope.async(() -> 666); 374 | list = scope 375 | .recover(ioException -> 333) 376 | .deadline(Instant.now().plus(1, ChronoUnit.SECONDS)) 377 | .await(Stream::toList); 378 | } 379 | assertEquals(List.of(666, 333, 111), list); 380 | } 381 | } -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/structured/StructuredScopeShutdownOnFailureTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.structured; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class StructuredScopeShutdownOnFailureTest { 10 | @Test 11 | public void oneTaskSuccess() throws InterruptedException { 12 | try(var scope = new StructuredScopeShutdownOnFailure()) { 13 | var supplier = scope.fork(() -> { 14 | Thread.sleep(100); 15 | return 42; 16 | }); 17 | scope.joinAll(); 18 | int value = supplier.get(); 19 | assertEquals(42, value); 20 | } 21 | } 22 | 23 | @Test 24 | public void oneTaskFailures() throws InterruptedException { 25 | try(var scope = new StructuredScopeShutdownOnFailure()) { 26 | var supplier = scope.fork(() -> { 27 | Thread.sleep(100); 28 | throw new IOException("boom"); 29 | }); 30 | try { 31 | scope.joinAll(); 32 | fail(); 33 | } catch (IOException e) { 34 | assertEquals("boom", e.getMessage()); 35 | } 36 | assertThrows(IllegalStateException.class, supplier::get); 37 | } 38 | } 39 | 40 | @Test 41 | public void oneTaskInterrupted() { 42 | try(var scope = new StructuredScopeShutdownOnFailure()) { 43 | var supplier = scope.fork(() -> { 44 | Thread.sleep(100); 45 | throw new InterruptedException(); 46 | }); 47 | try { 48 | scope.joinAll(); 49 | fail(); 50 | } catch(InterruptedException e) { 51 | // ok 52 | } 53 | 54 | assertThrows(IllegalStateException.class, supplier::get); 55 | } 56 | } 57 | 58 | @Test 59 | public void noTask() throws InterruptedException { 60 | try(var scope = new StructuredScopeShutdownOnFailure()) { 61 | scope.joinAll(); 62 | } 63 | } 64 | 65 | @Test 66 | public void manyTasksSuccess() throws InterruptedException{ 67 | try(var scope = new StructuredScopeShutdownOnFailure()) { 68 | var supplier1 = scope.fork(() -> { 69 | Thread.sleep(300); 70 | return 30; 71 | }); 72 | var supplier2 = scope.fork(() -> { 73 | Thread.sleep(100); 74 | return 10; 75 | }); 76 | scope.joinAll(); 77 | int value = supplier1.get(); 78 | int value2 = supplier2.get(); 79 | assertAll( 80 | () -> assertEquals(30, value), 81 | () -> assertEquals(10, value2) 82 | ); 83 | } 84 | } 85 | 86 | @Test 87 | public void manyTasksFailure() throws InterruptedException { 88 | try(var scope = new StructuredScopeShutdownOnFailure()) { 89 | var supplier = scope.fork(() -> { 90 | Thread.sleep(100); 91 | throw new IOException("boom"); 92 | }); 93 | var supplier2 = scope.fork(() -> { 94 | Thread.sleep(300); 95 | throw new IOException("boom2"); 96 | }); 97 | try { 98 | scope.joinAll(); 99 | fail(); 100 | } catch (IOException e) { 101 | assertEquals("boom", e.getMessage()); 102 | } 103 | assertAll( 104 | () -> assertThrows(IllegalStateException.class, supplier::get), 105 | () -> assertThrows(IllegalStateException.class, supplier2::get) 106 | ); 107 | } 108 | } 109 | 110 | @Test 111 | public void manyTasksMixedSuccessFailure() throws InterruptedException { 112 | try(var scope = new StructuredScopeShutdownOnFailure()) { 113 | var supplier = scope.fork(() -> { 114 | Thread.sleep(300); 115 | throw new IOException("boom"); 116 | }); 117 | var supplier2 = scope.fork(() -> { 118 | Thread.sleep(100); 119 | return 42; 120 | }); 121 | try { 122 | scope.joinAll(); 123 | fail(); 124 | } catch (IOException e) { 125 | assertEquals("boom", e.getMessage()); 126 | } 127 | assertAll( 128 | () -> assertThrows(IllegalStateException.class, supplier::get), 129 | () -> assertEquals(42, supplier2.get()) 130 | ); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/test/java/fr/umlv/loom/structured/StructuredScopeShutdownOnSuccessTest.java: -------------------------------------------------------------------------------- 1 | package fr.umlv.loom.structured; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class StructuredScopeShutdownOnSuccessTest { 10 | @Test 11 | public void oneTaskSuccess() throws InterruptedException { 12 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 13 | scope.fork(() -> { 14 | Thread.sleep(100); 15 | return 42; 16 | }); 17 | int value = scope.joinAll(); 18 | assertEquals(42, value); 19 | } 20 | } 21 | 22 | @Test 23 | public void oneTaskFailures() throws InterruptedException { 24 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 25 | scope.fork(() -> { 26 | Thread.sleep(100); 27 | throw new IOException("boom"); 28 | }); 29 | try { 30 | scope.joinAll(); 31 | fail(); 32 | } catch (IOException e) { 33 | assertEquals("boom", e.getMessage()); 34 | } 35 | } 36 | } 37 | 38 | @Test 39 | public void oneTaskInterrupted() throws InterruptedException { 40 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 41 | scope.fork(() -> { 42 | Thread.sleep(100); 43 | throw new InterruptedException(); 44 | }); 45 | try { 46 | scope.joinAll(); 47 | fail(); 48 | } catch(InterruptedException e) { 49 | // ok 50 | } 51 | } 52 | } 53 | 54 | @Test 55 | public void noTask() throws InterruptedException { 56 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 57 | assertThrows(IllegalStateException.class, scope::joinAll); 58 | } 59 | } 60 | 61 | @Test 62 | public void manyTasksSuccess() throws InterruptedException{ 63 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 64 | scope.fork(() -> { 65 | Thread.sleep(300); 66 | return 30; 67 | }); 68 | scope.fork(() -> { 69 | Thread.sleep(100); 70 | return 10; 71 | }); 72 | int value = scope.joinAll(); 73 | assertEquals(10, value); 74 | } 75 | } 76 | 77 | @Test 78 | public void manyTasksFailure() throws InterruptedException { 79 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 80 | scope.fork(() -> { 81 | Thread.sleep(100); 82 | throw new IOException("boom"); 83 | }); 84 | scope.fork(() -> { 85 | Thread.sleep(300); 86 | throw new IOException("boom2"); 87 | }); 88 | try { 89 | scope.joinAll(); 90 | fail(); 91 | } catch (IOException e) { 92 | assertEquals("boom", e.getMessage()); 93 | } 94 | } 95 | } 96 | 97 | @Test 98 | public void manyTasksMixedSuccessFailure() throws InterruptedException, IOException { 99 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 100 | scope.fork(() -> { 101 | Thread.sleep(100); 102 | throw new IOException("boom"); 103 | }); 104 | scope.fork(() -> { 105 | Thread.sleep(300); 106 | return 42; 107 | }); 108 | int value = scope.joinAll(); 109 | assertEquals(42, value); 110 | } 111 | } 112 | 113 | @Test 114 | public void manyTasksMixedSuccessFailure2() throws InterruptedException, IOException { 115 | try(var scope = new StructuredScopeShutdownOnSuccess()) { 116 | scope.fork(() -> { 117 | Thread.sleep(300); 118 | throw new IOException("boom"); 119 | }); 120 | scope.fork(() -> { 121 | Thread.sleep(100); 122 | return 42; 123 | }); 124 | int value = scope.joinAll(); 125 | assertEquals(42, value); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 69 | 131 | 132 | 133 |
134 | 135 | 136 |
137 |
138 |
139 | 140 | --------------------------------------------------------------------------------