Structured concurrency: TaskHandle
forax at univ-mlv.fr
forax at univ-mlv.fr
Fri May 12 18:56:35 UTC 2023
----- Original Message -----
> From: "Ron Pressler" <ron.pressler at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>, "Alan Bateman" <alan.bateman at oracle.com>
> Cc: "loom-dev" <loom-dev at openjdk.org>
> Sent: Friday, May 12, 2023 7:39:25 PM
> Subject: Re: Structured concurrency: TaskHandle
> I think there’s a point here that’s worth an emphasis:
>
> When join completes what we know is that every fork has either terminated *or*
> deemed irrelevant by a call to shutdown that tells the scope: “I have all the
> results I need to finish the parent tasks (successfully or not) and I don’t
> care about further results”. It is only when close returns that we know that
> all forks have actually terminated.
>
> I.e.:
>
> 1. After join: I have all I need to compute a result (but some forks might still
> be running).
>
> 2. After close: All forks have terminated.
>
> It is useful not to require that all forks have terminated when join completes
> because join can complete abnormally if interrupted. In that situation, the
> scope can decide whether to shutdown (cancelling all threads) or not. If
> InterruptedException is not explicitly handle (i.e. the “default”) then the
> scope will shut down because the exception will then trigger an implicit close,
> which will shutdown the scope; close is not interruptible.
>
> — Ron
I was thinking of this peculiar scenario, where the states are read after a call to join()
try(var scope = new StructuredTaskScope<Double>()) {
var future1 = scope.fork(() -> {
var result = 0.5;
for(var i = 0; i < 10_000_000; i++) {
result = Math.sin(result) + result;
}
return result;
});
var future2 = scope.fork(() -> {
Thread.sleep(1_000);
throw new IOException();
});
Thread.sleep(1);
scope.shutdown();
scope.join();
System.out.println("future 1: " + future1.state());
System.out.println("future 2: " + future2.state());
}
Rémi
>
>
>> On 12 May 2023, at 13:14, Alan Bateman <Alan.Bateman at oracle.com> wrote:
>>
>> On 12/05/2023 16:01, forax at univ-mlv.fr wrote:
>>> :
>>> I would say it in the other way, you need state() to know if the task has
>>> competed successfully then you can call get() or use the handle as Supplier.
>>> Technically, you do not need state() before calling join().
>>
>> It will take time to get more feedback from real world usages. The examples that
>> we published using ShutdownOnXXX don't need to switch on the state. For
>> ShutdownOnSuccess you can discard the object returned by fork, it's not needed.
>> For ShutdownOnFailure, if join().throwIfFailed() doesn't throw then you are
>> guaranteed that all tasks have completed successfully so you don't need the
>> state either.
>>
>>
>>> The non-determinism comes from the way Thread.interrupt() works. If the thread
>>> is interrupted during a blocking call or reach a blocking call an
>>> InterruptedException will be thrown. If there is no blocking call or
>>> Thread.currentThread().interrupt() is called, only the flag is positioned.
>>> I proposed that if the flag is positioned then the state of the task should be
>>> FAILED and if exception() is called, an InterruptException should be thrown
>>> (one with no stacktrace so it can be shared).
>> Calling shutdown means you are done and not interested in the tasks that are
>> still running. Yes, they are interrupted (after the state transition to
>> shutdown so any result/exceptions from the remaining task will be discarded).
>> Are you looking to capture how they reacted to interrupt or what do you mean by
>> "the flag is positioned". Maybe you are thinking about some side effect?
>>
>>> :
>>> Taking a step back, there are two states, there are the task state and the state
>>> on the object passed to handleComplete.
>>> The former can be RUNNING, SUCCESS, FAILED or CANCELLED, the later is only
>>> SUCCESS or FAILED.
>>> Ìf we have two different objects, each one can have a different enum, TaskState
>>> and ResultState an everything is fine.
>>> If we have use TaskHandle for both, switching on the state inside handleComplete
>>> has two unreachable states but the compiler does not know that.
>>>
>>> So perhaps the solution is to have two different states.
>> That might be hard to explain so I think we should wait for more real-world
>> usage and feedback before seeing if there are any adjustments required.
>>
>>
>>> :
>>> Another question does the API of TaskHandle should be only available to the
>>> owner thread of the scope ?
>> fork can be called by any subtask in the tree. So while of limited value
>> compared to Future in this context, it would surprising if every method threw
>> WTE.
>>
>> -Alan
>>
>>
More information about the loom-dev
mailing list