|
| 1 | +The virtual threads in Java 21 is an exciting and transformative new feature. |
| 2 | + |
| 3 | +All your server code that currently uses various async frameworks with chained callbacks can be gone. |
| 4 | + |
| 5 | +Already, you can write intuitive code to call outgoing rpc, wait for the result, handle any exception that may occur all in a single method. You won't be guilty of blocking precious platform thread and in turn degrading your server's throughput. |
| 6 | + |
| 7 | +But sometimes when you serve a request, you need to invoke several outgoing rpcs, or read from db. Doing them sequentially results in longer request latency and you spend most of the time waiting. |
| 8 | + |
| 9 | +It makes sense to do these IO-bound operations concurrently. [JEP 453](https://openjdk.org/jeps/453) is still in preview. And the API as it is still feels a bit verbose. So we created a simpler API ([Fanout](https://google.github.io/mug/apidocs/com/google/mu/util/concurrent/Fanout.html)). It focuses on the most common fanout use case where the [ShutDownOnFailure](https://docs.oracle.com/en/java/javase/20/docs/api/jdk.incubator.concurrent/jdk/incubator/concurrent/StructuredTaskScope.ShutdownOnFailure.html) stragegy is sufficient. |
| 10 | + |
| 11 | +For example if you need to concurrently read from db, and invoke a rpc to fetch some data: |
| 12 | + |
| 13 | +```java {.good} |
| 14 | +import static com.google.mu.util.concurrent.Fanout.concurrently; |
| 15 | + |
| 16 | +... |
| 17 | +Result calculateBilling() { |
| 18 | + return concurrently( |
| 19 | + () -> readJobTimelineFromDb(...), |
| 20 | + () -> callServiceForLatestAccountInfo(...), |
| 21 | + (timeline, accountInfo) -> ...); |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +Up to 5 concurrent fanout operations are supported. |
| 26 | + |
| 27 | +What you get: |
| 28 | + |
| 29 | +* Reading from db and calling rpcs are executed concurrently in virtual threads. |
| 30 | +* Return values from the concurrent operations are passed to the lambda to be combined. |
| 31 | +* If any of the concurrent operations fails, the other concurrent operation(s) are canceled, and the exception is propagated to the main thread so you can catch and handle it. |
| 32 | +* If the main thread is interrupted while waiting for the concurrent operations, the currently ongoing concurrent operations will be canceled. |
| 33 | + |
| 34 | +By default, the library uses `Executors.newVirtualThreadPerTaskExecutor()` to run the concurrent tasks in virtual threads. But sometimes your org may want to enforce extra context propagation, monitoring or profiling that require a special `Executor`. For these cases, you can create a subclass of `StructuredConcurrencyExecutorPlugin` and implement the `createExecutor()` method. Then put the class name in the `META-INF/services/com.google.mu.util.concurrent.StructuredConcurrencyExecutorPlugin` file for it to be accessible through `java.util.ServiceLoader`. |
| 35 | + |
| 36 | +Optionally you can use Google [`@AutoService`](http://github.com/google/auto/tree/main/service) to automate the `META-INF` part. |
0 commit comments