Structured concurrency: TaskHandle

forax at univ-mlv.fr forax at univ-mlv.fr
Fri May 12 21:54:33 UTC 2023


----- Original Message -----
> From: "Alan Bateman" <Alan.Bateman at oracle.com>
> To: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "loom-dev" <loom-dev at openjdk.org>
> Sent: Friday, May 12, 2023 7:14:02 PM
> Subject: Re: Structured concurrency: TaskHandle

> 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 ?

I'm thinking about the case where a user take a look at the state() after a call to shutdown() followed by a call to join(),
see the example in my email to Ron.

> 
>> :
>> 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.

There is a way to avoid to take a look to state() before either using get() or exception(), use "exception transparency".
In the case of a synchronous code, when a method is called either the return value is available or an exception is thrown, why not do the same ?

Practically, it means that get() can either return a T or throws an E, so TaskHandle is parametrized by both T and E

  interface TaskHandle<T, E extends Exception> {
    T get() throws E, InterruptedException;
    ...
  }

We also need TaskHandle to be able to throw an InterruptedException if the task has been cancelled by STS.shutdown().

In that case, we can not use Callable because Callable in not parametrized by the type of the exception, so the compiler can not propagate the type.
We need a new functional interface

  interface Invokable<T, E extends Exception> {
     T invoke() throws E, InterruptedException;
  }

And STS.fork() will propagate both the type of the return value and the type of the exception of the Invokable to the TaskHandle
 class STS<T, E extends Exception> {
    public <U extends T, F extends E> TaskHandle<U, F> fork(Invokable<? extends U, ? extends F> invokable) { ... }
    ...
 }


> 
> 
>> :
>> 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.

yes right.

> 
> -Alan


Rémi


More information about the loom-dev mailing list