Example in support of CompletionHandler
Anthony Vanelverdinghe
dev at anthonyv.be
Mon Nov 14 17:33:12 UTC 2022
(For a Markdown-rendered version, see:
https://gist.github.com/anthonyvdotbe/7378020533aaced3fe4d19d3649bb98e )
While upgrading my Loom experiments to the current API, I noticed
`CompletionHandler` has disappeared.
One of the experiments was to process a file by splitting it into blocks
and processing each block in parallel.
To do so, I created a generic method which would do (1) the
block-splitting, (2) map each block to a result, and (3) collect the
results.
For (2) and (3) I'd pass in a functional interface as argument.
The method I had was something like:
```java
<T> void process(Path file, Function<ByteBuffer, T> mapper,
CompletionHandler<? super T> collector) {
try (var executor = StructuredExecutor.open()) {
for(each block) {
executor.fork(() -> mapper.apply(block), collector);
}
executor.join();
}
}
```
And it would be used like:
```java
V result() {
var collector = ...;
process(file, mapper, collector);
return collector.result();
}
```
To upgrade to the current API, the minimal change was:
```java
<T> void process(Path file, Function<ByteBuffer, T> mapper,
StructuredTaskScope<? super T> scope) {
try (scope) {
for(each block) {
scope.fork(() -> mapper.apply(block));
}
scope.join();
}
}
```
While this works, it's ugly having to pass `StructuredTaskScope`
instances around.
(Plus that the thread creating a `StructuredTaskScope` instance becomes
its owner,
so in case the instance gets handed over to another thread while being
passed around,
I guess this might result in unexpected behavior.)
So instead, I extended `StructuredTaskScope` to "bring back" the old API:
```java
final class Delegate<T> extends StructuredTaskScope<T> {
interface CompletionHandler<U> {
void handle(StructuredTaskScope<U> scope, Future<U> future);
}
private final CompletionHandler<T> handler;
Delegate(CompletionHandler<T> handler) {
this.handler = Objects.requireNonNull(handler);
}
@Override
protected void handleComplete(Future<T> future) {
Objects.requireNonNull(future);
handler.handle(this, future);
}
}
```
(I don't like that, unlike with the old API, both parameters of
`CompletionHandler::handle` have a type argument now. It seems to
indicate there's something unnatural going on.
And I tried to improve the generics with bounded wildcards, but all my
attempts resulted in a compiler error for the `handler.handle(this,
future);` invocation.)
This allowed me to write:
```java
<T> void process(Path file, Function<ByteBuffer, T> mapper,
CompletionHandler<? super T> collector) {
try (var scope = new Delegate<>(collector)) {
for(each block) {
scope.fork(() -> mapper.apply(block));
}
scope.join();
}
}
```
This is better than passing the `StructuredTaskScope` around, but I'm
not particularly fond of the `Delegate` class.
Another option would be to bring back `CompletionHandler`, but instead
of it being a parameter of the `fork` method, make it a constructor
parameter.
So then I'd be able to write:
```java
<T> void process(Path file, Function<ByteBuffer, T> mapper,
CompletionHandler<? super T> collector) {
try (var scope = new StructuredTaskScope<>(collector)) {
for(each block) {
executor.fork(() -> mapper.apply(block));
}
executor.join();
}
}
```
To conclude: I believe the old API was a better fit for use cases like
the above. (In case I missed an equally good solution with the new API,
I'm all ears.)
Therefore I'd like to propose to do one of the following:
(a) bring back the old API
(b) bring back the old API, but move the `CompletionHandler` parameter
from the `fork` method to the constructor
Or, if the above aren't an option:
(c) provide something like `Delegate` above, in addition to
`ShutdownOn(Success|Failure)`
Kind regards,
Anthony
More information about the loom-dev
mailing list