Skip to content

Instantly share code, notes, and snippets.

@paul-bjorkstrand
Last active November 22, 2021 20:48
Show Gist options
  • Save paul-bjorkstrand/43bb8335453aa1eccb982b54556d7633 to your computer and use it in GitHub Desktop.
Save paul-bjorkstrand/43bb8335453aa1eccb982b54556d7633 to your computer and use it in GitHub Desktop.
Loom snippets
// Roguh translation for my first approach, that had no way to an
// underlying error that occurred during execution of the tasks
class test {
public <V> List<V> test(List<Callable<V>> someTasks) {
try (var se = StructuredExecutor.open()) {
var futures = someTasks.stream()
.map(se::fork);
se.join();
return futures.map(Future::resultNow)
.toList();
}
}
}
// My first attempt at solving the visibility problem for the exception
// see cleanSnippet.java for the simple example
class test {
public <V> List<V> test(List<Callable<V>> someTasks) {
try (var se = StructuredExecutor.open()) {
var futures = someTasks.stream()
.map(se::fork)
.toList();
se.join();
futures.stream()
.filter(f -> f.state() == Future.State.FAILED)
.findAny()
.ifPresent(f -> throw new RuntimeException(f.exceptionNow()));
return futures.stream()
.map(Future::resultNow)
.toList();
}
}
}
// After Brian's email explaining more on .resultNow(), an example on how to solve the problem
// in a canonical manner
public final class TrackFirstFailure implements BiConsumer<StructuredExecutor, Future<Object>> {
// ... similar state as ShutdownOnFailure ...
public void accept(StructuredExecutor executor, Future<Object> future) {
// ... same code as ShutdownOnFailure.accept, but without the call to executor.shutdown()
}
// ... exact same remaining impl as ShutdownOnFailure ...
}
class test {
public <V> List<V> test(List<Callable<V>> someTasks) {
try (var se = StructuredExecutor.open()) {
var trackingPolicy = new TrackFirstFailure();
var futures = someTasks.stream()
.map(task -> se.fork(task, trackingPolicy);
se.join();
trackingPolicy.throwIfFailed();
return futures.map(Future::resultNow)
.toList();
}
}
}
// A further example, where StructuredExecutor has a built-in handler to track the first-failed task
// This impl can be built into StructuredExecutor, or an instance can be exposed by StructuredExecutor
public final class TrackFirstFailure implements BiConsumer<StructuredExecutor, Future<Object>> {
// ... similar state as ShutdownOnFailure ...
public void accept(StructuredExecutor executor, Future<Object> future) {
// ... same code as ShutdownOnFailure.accept, but without the call to executor.shutdown()
}
// ... exact same remaining impl as ShutdownOnFailure ...
}
class test {
public <V> List<V> test(List<Callable<V>> someTasks) {
try (var se = StructuredExecutor.open()) {
var futures = someTasks.stream()
.map(se::fork);
se.join();
se.throwIfFailed();
// Or, se.handler().throwIfFailed();
return futures.map(Future::resultNow)
.toList();
}
}
}
// A further example, with overload(s) for StructuredExecutor.open()
public final class TrackFirstFailure implements BiConsumer<StructuredExecutor, Future<Object>> {
// ... similar state as ShutdownOnFailure ...
public void accept(StructuredExecutor executor, Future<Object> future) {
// ... same code as ShutdownOnFailure.accept, but without the call to executor.shutdown()
}
// ... exact same remaining impl as ShutdownOnFailure ...
}
class test {
public <V> List<V> test(List<Callable<V>> someTasks) {
var trackingPolicy = new TrackFirstFailure();
// This handler is used as a default when no handler is provided to .fork()
// This will likely add additional complexity that may prove unnecessary,
// but it may be useful for simple code
try (var se = StructuredExecutor.open(trackingPolicy)) {
var futures = someTasks.stream()
.map(se::fork); // no need to add the handler here
se.join();
trackingPolicy.throwIfFailed();
return futures.map(Future::resultNow)
.toList();
}
}
}
@paul-bjorkstrand
Copy link
Author

cleanExample.java shows a naive understanding of StructuredExecutors.

lessCleanExample.java shows a clunky way to resolve it, due to a lack of understanding in how the "handlers" are used.

newHandlerEcample.java shows, perhaps, the simplest way to approach the situation. It would be nice if this handler were at least part of the first preview release, and would cover many use cases where the exception is only needed for bubbling up and/or logging.

newHandlerWithOverload.java is a more complicated way, but allows developers to define a "default" handler, when none is specified in the .fork() calls.

newHandlerPartOfTheExecutor.java builds the "tracking" handler into StructuredExecutor itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment