<div dir="auto"><div>The way to handle these cases is to put the logic after the task ends into the scope itself.</div><div dir="auto"><br></div><div dir="auto">For example:</div><div dir="auto"><br></div><div dir="auto">class ContinuableScope<T> extends StructuredTaskScope<T, K> {</div><div dir="auto">@FunctionalInterface</div><div dir="auto"> public interface FanLogic<T, K> {</div><div dir="auto"> K get(TaskHandle<T> handle) throws Throwable;</div><div dir="auto"> }</div><div dir="auto"> private final Queue<K> results = new ConcurrentLinkedQueue<>();</div><div dir="auto"> private final Queue<Throwable> exceptions = new ConcurrentLinkedQueue<>();</div><div dir="auto"><br></div><div dir="auto"> ContinuableScope(FanLogic<T,K> next, ShutdownStrategy<K> strategy) {</div><div dir="auto"> this.next = next;</div><div dir="auto"> this.strategt = strategy;</div><div dir="auto"> super(null, Thread.ofVirtual().factory()); </div><div dir="auto"> }</div><div dir="auto"><br></div><div dir="auto"> @Override</div><div dir="auto"> protected void handleComplete(TaskHandle<T> handle) {</div><div dir="auto"> try {</div><div dir="auto"> var nr = next.get(handle);</div><div dir="auto"> if (nr == null) return; // using null to not make this more complicated in the mail</div><div dir="auto"> if (strategy.testResult(nr, results, exceptions)) {</div><div dir="auto"> // Kill the scope </div><div dir="auto"> return;</div><div dir="auto"> }</div><div dir="auto"> results.add(nr);</div><div dir="auto"> } catch(Throwable e) {</div><div dir="auto"> if (strategy.testFailure(e, results, exceptions)) {</div><div dir="auto"> // Kill the scope </div><div dir="auto"> return;</div><div dir="auto"> }</div><div dir="auto"> exceptions.add(nr);</div><div dir="auto"> }</div><div dir="auto"> }</div><div dir="auto"><br></div><div dir="auto"> public Stream<T> results() {</div><div dir="auto"> super.ensureOwnerAndJoined();</div><div dir="auto"> return results.stream();</div><div dir="auto"> }</div><div dir="auto"> public Stream<Throwable> exceptions() {</div><div dir="auto"> super.ensureOwnerAndJoined();</div><div dir="auto"> return exceptions.stream();</div><div dir="auto"> }</div><div dir="auto">}</div><div dir="auto"><br></div><div dir="auto">---</div><div dir="auto"><br></div><div dir="auto">public void main() {</div><div dir="auto"> var strategy = new ShutdownStrategy<String>(...); // Shut down on unknown host, and on results length == 3</div><div dir="auto"> var fanAction = (th) -> {</div><div dir="auto"> var r = transform(th);</div><div dir="auto"> if (validate(r)) return r;</div><div dir="auto"> return null; //again, using null for briefity</div><div dir="auto"> }</div><div dir="auto"> try (var scope = new ContinuableScope<Result>(fanAction, strategy)) {</div><div dir="auto"> for (...) {</div><div dir="auto"> scope.fork(() -> call(...));</div><div dir="auto"> }</div><div dir="auto"> scope.join();</div><div dir="auto"> if (scope.results().isEmpty()) {</div><div dir="auto"> throw ...</div><div dir="auto"> }</div><div dir="auto"> // Return an object storing both scope.results() and scope.exceptions()</div><div dir="auto"> }</div><div dir="auto">}</div><div dir="auto"><br></div><div dir="auto"><br></div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote" dir="auto"><div dir="ltr" class="gmail_attr">On Wed, May 10, 2023, 01:39 <<a href="mailto:forax@univ-mlv.fr" rel="noreferrer noreferrer" target="_blank">forax@univ-mlv.fr</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">----- Original Message -----<br>
> From: "Ron Pressler" <<a href="mailto:ron.pressler@oracle.com" rel="noreferrer noreferrer noreferrer" target="_blank">ron.pressler@oracle.com</a>><br>
> To: "Remi Forax" <<a href="mailto:forax@univ-mlv.fr" rel="noreferrer noreferrer noreferrer" target="_blank">forax@univ-mlv.fr</a>><br>
> Cc: "Alan Bateman" <<a href="mailto:alan.bateman@oracle.com" rel="noreferrer noreferrer noreferrer" target="_blank">alan.bateman@oracle.com</a>>, "loom-dev" <<a href="mailto:loom-dev@openjdk.java.net" rel="noreferrer noreferrer noreferrer" target="_blank">loom-dev@openjdk.java.net</a>><br>
> Sent: Tuesday, May 9, 2023 10:33:25 PM<br>
> Subject: Re: Structured Concurrency yet again<br>
<br>
>> On 9 May 2023, at 15:29, <a href="mailto:forax@univ-mlv.fr" rel="noreferrer noreferrer noreferrer" target="_blank">forax@univ-mlv.fr</a> wrote:<br>
>> <br>
>> <br>
>> It is interesting to note that we seem to agree that fork() should not return a<br>
>> Future. I can identify two logical consequences.<br>
>> <br>
>> Firstly, as mentioned in the JEP, get() or state() should only be called after<br>
>> joinAll(). Calling task.state() on a running computation would throw an<br>
>> IllegalStateException, making State.RUNNING unnecessary. Furthermore, when<br>
>> scope.shutdown() is invoked, a cancelled task can be in state FAILED or<br>
>> CANCELLED, as explained in the documentation, which is a pain to deal with as a<br>
>> user. I propose to simplify the design and remove the state CANCELLED (perhaps<br>
>> making it a sub-state of FAILED with a method isCancelled() if we want to keep<br>
>> it). This way, users will have only to deal with two states SUCCESS or FAILED.<br>
>> It is worth noting that existing reactive APIs do not differentiate between<br>
>> CANCELLED and FAILED too.<br>
>> <br>
>> The second consequence concerns the TaskHandle.exception() method. It should not<br>
>> return a Throwable, since Callable throws an Exception and storing an Error in<br>
>> a field is not a good practice. Exception handling is a significant concern in<br>
>> Java, and we have the opportunity to provide a transparent way to address it<br>
>> without being constrained by the design of Future. A TaskHandle can be<br>
>> parameterized by both the result type and the exception type. When calling<br>
>> get(), exceptions are transparently re-spawned. However, this means that<br>
>> TaskHandle cannot be a Supplier.<br>
>> <br>
>> Then I prefer to use a Stream to deal with the different semantics of a scope<br>
>> instead of asking to subclass it and hoping that users will correctly manage<br>
>> the concurrency when overriding handleComplete().<br>
>> <br>
>>> <br>
>>> -Alan.<br>
>>> <br>
>>> [1] <a href="https://openjdk.org/jeps/8306641" rel="noreferrer noreferrer noreferrer noreferrer" target="_blank">https://openjdk.org/jeps/8306641</a><br>
>> <br>
>> regards,<br>
>> Rémi<br>
> <br>
> When a policy is used, there is no need for the scope to ever call either<br>
> `state` or `exception` on subtasks’ handles; these methods are intended to be<br>
> used only by handleComplete (or from the scope only if no policy is used, but<br>
> preferably one should be). As the JEP describes, all exceptions should be<br>
> handled centrally, as part of the policy, before calling TaskHandle.get(), the<br>
> only TaskHandle method the scope should call when using a policy. Could you<br>
> show what problems you’ve run into with exceptions when policies are used ?<br>
<br>
Usually when you do something synchronous with some network calls, you mix an algorithm (i need at most the first three valid values) with some IO/Network specific concerns (i want to stop if the is an UnknownHostException (aka DNS error) but an IOException (aka unknown network error) is fine).<br>
<br>
In term of synchronous code, it's something like<br>
<br>
var list = new ArrayList<Result>();<br>
var error = (IOException) null;<br>
for(...) {<br>
Result result;<br>
try { <br>
result = synchronousCall(...);<br>
} catch(UnknownHostException e) {<br>
throw ... (e);<br>
} catch(IOException e) {<br>
if (error == null) {<br>
error = e; // just record the error<br>
}<br>
continue;<br>
}<br>
if (valid(result)) {<br>
list.add(result);<br>
if (list.size() == 3) {<br>
break;<br>
}<br>
}<br>
}<br>
if (list.isEmpty()) {<br>
throw ... (error);<br>
}<br>
...<br>
<br>
How can i write this kind of stuff as a reusable policy that centralizes all the exceptions ?<br>
<br>
> <br>
> — Ron<br>
<br>
Rémi<br>
</blockquote></div></div></div>