Structured concurrency: TaskHandle

Ron Pressler ron.pressler at oracle.com
Fri May 12 17:39:25 UTC 2023


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


> 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