[External] : Re: My experience with Structured Concurrency

David Alayachew davidalayachew at gmail.com
Tue Aug 19 15:02:48 UTC 2025


Yeah, I agree. Ultimately, composition was a tactic that I attempted that
didn't work out. My original post was exploring tactics like that, and, due
to things not working out, started asking for a factory method.

At this point, thanks to Robert Engels pointing me back to the CS
fundamentals (other branch of this thread), my needs have been met --
albeit, with a lot more syntactic overhead than I would have liked.

My only real ask now would just be if there was some AbstractJoiner
implementation that made creating stateful joiners easier. And while I
don't desperately need it (making a few AbstractJoiners myself isn't harrd,
and solves my problem), I do think there is value in it anyways, just on
how many stateful joiners I ended up creating in the end.

On Tue, Aug 19, 2025, 5:16 AM Viktor Klang <viktor.klang at oracle.com> wrote:

> Ok, got it.
>
> I think it is wise to avoid scope-creep on the responsibilities of the
> Joiner—let's say you created the following:
>
> <T,A,R,AA,RR> Joiner<T, RR> custom(Gatherer<T,A,R> g, Collector<R,AA,RR>
> c) // variance annotations omitted for brevity
>
> It might be very tempting to start doing things like:
>
> try ( var scope =
> StructuredTaskScope.open(custom(Gatherers.windowFixed(…).andThen(MyGatherers.flatMap(…)).andThen(MyGatherers.limit(10)),
> Collectors.toList())) {
>    ...
>    scope.join();
>    ...
> }
>
> Which, in my mind, would make it difficult to reason about the behavior of
> a StructuredTaskScope—and it would be exceedingly easy for business logic
> to start leaking into the Joiner itself.
>
> Cheers,
>>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
> ------------------------------
> *From:* David Alayachew <davidalayachew at gmail.com>
> *Sent:* Monday, 18 August 2025 17:25
> *To:* Viktor Klang <viktor.klang at oracle.com>
> *Cc:* loom-dev <loom-dev at openjdk.org>
> *Subject:* Re: [External] : Re: My experience with Structured Concurrency
>
> I definitely see what you are talking about.
>
> I did originally consider that, and while that helped obviate the need of
> some joiners, it also didn't help for many at all.
>
> That said, the other branch of this thread seems to have reached a
> conclusion -- I could just make use of Abstract Classes to create a small
> handful of common AbstractJoiners, then use anonymous classes to fill in
> the details inline. It skips the whole naming problem, as I am naming the
> general categories of each Joiner, rather than each and every single one.
>
> I will say, while this solution works, I am still partial to the idea of
> having a factory or something that would make the creation of these
> factories simpler to do. An anonymous class implementing an abstract class
> inline is a little hard to read imo.
>
> Nonetheless, with the Abstract class suggestion, I can no longer say that
> the API makes it hard to make a bunch of similar joiners. It is merely
> inconvenient and a little clunky, imo.
>
> On Mon, Aug 18, 2025, 6:14 AM Viktor Klang <viktor.klang at oracle.com>
> wrote:
>
> Trying to come up with an example on the spot is always challenging, so if
> the following doesn't make sense in your case(s) let me know (and, fair
> warning, I just typed it into the email client, so this is unlikely to
> compile as-is).
>
> Now, imagine that you want to create the following logical structure of
> tasks (note that these are all concurrent): *(A and B) or C*
>
> You could go down the path of creating a bespoke Joiner that handles this
> case specifically, and then when you have *(A and B and C) or D or E *you're
> likely to have to create yet another such custom thing (repeat for
> permutations thereof).
>
> If you were to instead imagine you have the following two different
> joiners:
>
> MyJoiners.<T>and() // takes tasks which produce a result of type T and
> joins them into a List<T> with all the results if all tasks are successful,
> and fails if any of them fail (see: Joiners.allSuccessfulOrThrow())
> MyJoiners.<T>or() // takes tasks which produce a result of type T and
> returns the first result to complete successfully, and fails if all fail
> (see: Joiners.anySuccessfulResultOrThrow())
>
> That would be equivalent to:
>
> try (var orScope = StructuredTaskScope.open(MyJoiners.<List<String>>or()))
> {
>     orScope.fork(() -> {
>         try (var andScope =
> StructuredTaskScope.open(MyJoiners.<String>and())) {
>             andScope.fork(() -> "A");
>             andScope.fork(() -> "B");
>             return andScope.join();
>         }
>     });
>
>     orScope.fork(() -> List.of("C"));
>
>     return orScope.join(); // either ["A", "B"] or ["C"]
> }
>
> So while this is merely a (possibly contrived) example, I hope that it
> illustrates the notion of composition of scopes rather than the composition
> of Joiners.
>
> Cheers,
>>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
> ------------------------------
> *From:* David Alayachew <davidalayachew at gmail.com>
> *Sent:* Sunday, 17 August 2025 22:50
> *To:* Viktor Klang <viktor.klang at oracle.com>
> *Cc:* loom-dev <loom-dev at openjdk.org>
> *Subject:* Re: [External] : Re: My experience with Structured Concurrency
>
> Hey, I've been trying out your idea, but I can't find a single place where
> doing it would apply.
>
> I understand the idea of nesting scopes well enough. For example, I could
> put a innerScope inside of a outerscope.fork() call. Alternatively, I could
> have another scope after the outerScope.join() call.
>
> But doing it the first way would mean that I am creating an entire scope
> for each outerScope subtask. Is that what you are proposing? That makes
> sense if there are multiple tasks worth of processing that I want. For
> example, if the outerscope subtask would return a list of things, each of
> which, I could do further processing on. But that doesn't make sense if
> each outerScope subtask has only one "thing" that needs further downstream
> processing. The entire point of a scope is to do multi-threading. Having
> one task defeats the purpose of that.
>
> And doing it the second way means I would have to wait for all of my tasks
> to complete from the outerScope before the innerScope could do anything.
>
> In both cases, I wouldn't really be able to gain anything for my composed
> joiners.
>
> And to be clear, most of my Joiner compositions were simply nesting and
> adding to an existing implementation. For example, I had a joiner that
> closed the scope after 3 counts of tasks failing with SomeException. Well,
> I then used composition to make it cancel AND return the tasks up to and
> including the 3 failed. In that case, I don't see how I would gain anything
> by adding another scope.
>
> Could you help me see what you had in mind? Maybe I am just lacking
> creativity.
>
>
>
>
> On Fri, Aug 15, 2025 at 8:04 PM David Alayachew <davidalayachew at gmail.com>
> wrote:
>
> Oh, I'm already doing that. Lol, I love STS BECAUSE nesting scopes is so
> easy to do.
>
> But that's an interesting idea. I kind of see what you mean -- maybe I am
> stuffing too much logic into a single Joiner. I can't tell yet, so I guess
> I'll just have to try it out and get back to you.
>
> Thanks for the tip.
>
>
> On Fri, Aug 15, 2025, 5:09 PM Viktor Klang <viktor.klang at oracle.com>
> wrote:
>
> Hi David,
>
> Thanks for the added detail, that really helps my understanding of your
> situation.
>
> Did you try/consider/evaluate nested scopes (each with different Joiner
> strategies) over composing Joiners themselves?
> And if you did, what were your findings when comparing those two different
> approaches?
>
> Cheers,
>>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
> ------------------------------
> *From:* David Alayachew <davidalayachew at gmail.com>
> *Sent:* Friday, 15 August 2025 20:53
> *To:* Viktor Klang <viktor.klang at oracle.com>
> *Cc:* loom-dev <loom-dev at openjdk.org>
> *Subject:* [External] : Re: My experience with Structured Concurrency
>
> One other detail I'd like to highlight.
>
> Much like Collectors and Gatherers, there are a handful of super useful
> ones that you use everywhere, and then the rest are ad-hoc, inline ones
> where you sort of just make your own to handle a custom scenario. If you
> use streams often, you will run into those frequently, and that's why those
> factory methods are fantastic.
>
> Well, I have kind of found myself in the same position for Joiners.
> Joiners aren't as complex as Collectors and Gatherers, so there has
> certainly been less need for it. But I am also only a few weeks into using
> Joiners (though, I used STS for over a year). If I feel this strain now,
> then I feel like this experience is definitely worth sharing.
>
> On Fri, Aug 15, 2025, 2:44 PM David Alayachew <davidalayachew at gmail.com>
> wrote:
>
> Sure.
>
> Long story short, the biggest reason why STS is so useful for me is
> because it allows me to fire off a bunch of requests, and handle their
> failures and outcomes centrally. That is the single most useful feature of
> this library for me. It's also why Future.status was not so useful for me
> -- it calls get under the hood, and therefore might fail! Handling that was
> too much scaffolding.
>
> So, when someone recently challenged me to use Joiners (rather than the
> old STS preview versions I was used to), I started creating Joiners to
> handle all sorts of failure and outcomes. At first, a lot of them could be
> handled by the Joiner.awaitUntil(), where I would just check and see if the
> task failed, then handle the error. But as I got further and further along,
> I started needing to add state to my Joiners in order to get the failure
> handling that I wanted. For example, if a certain number of timeouts occur,
> cancel the scope. Well, that necessitates an AtomicNumber.
>
> Then, as the error-handling got more and more complex, I started finding
> myself making a whole bunch of copy paste, minor variations of similar
> Joiners. Which isn't bad or wrong, but started to feel some strain. Now, I
> need to jump through an inheritance chain just to see what my Joiner is
> really doing. It wasn't so bad, but I did start to feel a little uneasy.
> Bad memories.
>
> So, the solution to a problem like this is to create a Joiner factory.
> Which is essentially what I started to write before I started remembering
> how Collectors and Gatherers worked. At that point, I kind of realized that
> this is worth suggesting, which prompted me to write my original email.
>
> Like I said, not a big deal if you don't give it to me -- I can just make
> my own.
>
> But yes, that is the surrounding context behind that quote. Let me know if
> you need more details.
>
>
> On Fri, Aug 15, 2025, 9:25 AM Viktor Klang <viktor.klang at oracle.com>
> wrote:
>
> Hi David,
>
> First of all—thank you for your feedback!
>
> I'm curious to learn more about why you ended up in the situation you
> describe below, specifically about what use-cases led you into wishing for
> an augmentation to Joiner to facilitate composition.
>
> Are you able to share more details?
>
> >Which, funnily enough, led to a slightly different problem -- I found
> myself wanting an easier way to create Joiners. Since I was leaning on
> Joiners so much more heavily than I was for STS, I ended up creating many
> Joiners that do almost the same thing, with just minor variations. And
> inheritance wasn't always the right answer, as I can't inherit from
> multiple classes. Plus, most of my joiners were stateful, but I only wanted
> the non-stateful parts of it. I could do composition, but it sort of felt
> weird to delegate to multiple other Joiners.
>
> Cheers,
>>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
> ------------------------------
> *From:* loom-dev <loom-dev-retn at openjdk.org> on behalf of David Alayachew
> <davidalayachew at gmail.com>
> *Sent:* Friday, 15 August 2025 11:52
> *To:* loom-dev <loom-dev at openjdk.org>
> *Subject:* My experience with Structured Concurrency
>
> Hello @loom-dev <loom-dev at openjdk.org>,
>
> I just wanted to share my experience with Structured Concurrency. I had
> actually been using it for a while now, but only recently got experience
> with the new Joiner. After trying it out, my previously stated opinion has
> changed.
>
> Overall, Structured Concurrency has been a pleasure. I'll avoid repeating
> ALL my old thoughts and just highlight the KEY details.
>
> * Structured Concurrency is excellent for complex error-handling.
> Receiving exceptions via the subtask makes all the error-handling less
> painful.
> * Structured Concurrency makes nesting scopes a breeze, a task I
> historically found very painful to do.
> * Inheritance allows me to take an existing Scope (now Joiner), and modify
> only what I need to in order to modify it for my use case. Great for
> reusing old strategies in new ways.
>
> Now for the new stuff -- having Joiner be the point of extension
> definitely proved to be the right move imo. I didn't mention this in my
> original message, but while it was easy to get a scope set up using
> inheritance, it wasn't always clear what invariants needed to be
> maintained. For example, the ensureOwnerAndJoined method. Was that
> something we needed to call when inheriting? On which methods? Just join()?
>
> The Joiner solution is comparatively simpler, which actually meant that I
> ended up creating way more Joiners, rather than only several STS'. Joiners
> invariants are obvious, and there is no ambiguity on what is expected from
> the implementor.
>
> Which, funnily enough, led to a slightly different problem -- I found
> myself wanting an easier way to create Joiners. Since I was leaning on
> Joiners so much more heavily than I was for STS, I ended up creating many
> Joiners that do almost the same thing, with just minor variations. And
> inheritance wasn't always the right answer, as I can't inherit from
> multiple classes. Plus, most of my joiners were stateful, but I only wanted
> the non-stateful parts of it. I could do composition, but it sort of felt
> weird to delegate to multiple other Joiners.
>
> Part of me kept wondering how well a factory method, similar to the ones
> for Collectors and Gatherers, might fare for Joiners.
>
> Regardless, even if we don't get that factory method, this library has
> been a pleasure, and I can't wait to properly implement this once it goes
> live.
>
> Thank you for your time and consideration.
> David Alayachew
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250819/6605b5bd/attachment-0001.htm>


More information about the loom-dev mailing list