Structured concurrency: TaskHandle

Attila Kelemen attila.kelemen85 at gmail.com
Fri May 12 20:05:42 UTC 2023


I really love Rémi's proposal about merging the `state`, `get` and
`exception` into a single call, otherwise it will push people towards
creative ways to check the result state creating multiple patterns for
it. While Rémi's sealed interface proposal just cannot be
misinterpreted. For example, consider the documentation of
`exception`: "Returns the exception, if the task failed with an
exception." It is not exactly obvious what if the task failed with an
exception due to a cancellation? In case shutdown was called before
the task completed the current proposal requires the cancelled state.
So, is that exception lost now? That seems to me a problem, because it
is perfectly possible that due to one task's action (done due to
cancellation) another went into a bad state, and you do want to make
it visible.

### Cancellation

Cancellation is important enough to have very special care in my
opinion. In fact - to me - it was so important that I wrote a brand
new executor framework for my usage and happily using that instead of
the built-in for 10+ years now.

The question that should be asked is what are the important cases for
cancellation?

1. The task did not even start. This is important to know, because it
is very easy to argue about the consequences of this case. Though not
completely free
2. The task did not complete before `shutdown`, but failed with a
specially interpreted type of exception.
3. The task did not complete before `shutdown` was called, but started
execution, and threw an unexpected exception.
4. The task did not complete before `shutdown` was called, but started
execution, and returned without an exception.

I think only (1) and (2) should be marked as cancelled, and this also
implies that - in Rémi's sealed interface proposal - the `Canceled`
result should have an nullable `Throwable` field.

Case 3 should be considered to be an error, as that means that
something unexpected happened (or at least someone was sloppy).

Case 4 should be considered a success. Maybe, if we really want to
preserve this information, then the success record could have a flag
for this (though I believe it would be widely unused).

As for Alan's comment about "storming handleComplete" on cancellation:
`handleComplete` must not be called for case (1). If the STS needs to
count them for whatever reason, then maybe a new
`handleNonExecuted(long)` method can be implemented. Storming would be
quite problematic for case (1), but not so much for the rest (since
they were in the process of doing something anyway).


### TaskHandle.task()

This method is a menace. This was one of the many cancellation problem
of the JDK's thread pool (though was not restricted to make this
mistake by its interface): I often needed the case (especially for UI)
where I bombarded an executor with tasks, but the tasks were
idempotent, so there is no reason to have more of them queued. Yet,
the cancellation of the JDK's thread pool did not remove the canceled
task from its queue upon cancellation retaining a reference to the
task object. This can be quite the issue, and this method on
`TaskHandle` will now force the JDK to always keep a reference to the
task, even though it is largely useless. If you really need it, you
have the option to retain it and associate it with the `TaskHandle`.
So, my opinion is that this must be removed in order to allow the GC
to reclaim the task and whatever potentially large object it is
referencing.

If we really want to provide this for the STS (because that is the
only place which would not need about it otherwise), then that could
be provided in a separate argument, and that would also allow the GC
to reclaim the task.

Attila


More information about the loom-dev mailing list