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 <[email protected]>
Sent: Sunday, 17 August 2025 22:50
To: Viktor Klang <[email protected]>
Cc: loom-dev <[email protected]>
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 
<[email protected]<mailto:[email protected]>> 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 
<[email protected]<mailto:[email protected]>> 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 
<[email protected]<mailto:[email protected]>>
Sent: Friday, 15 August 2025 20:53
To: Viktor Klang <[email protected]<mailto:[email protected]>>
Cc: loom-dev <[email protected]<mailto:[email protected]>>
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 
<[email protected]<mailto:[email protected]>> 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 
<[email protected]<mailto:[email protected]>> 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 <[email protected]<mailto:[email protected]>> on 
behalf of David Alayachew 
<[email protected]<mailto:[email protected]>>
Sent: Friday, 15 August 2025 11:52
To: loom-dev <[email protected]<mailto:[email protected]>>
Subject: My experience with Structured Concurrency

Hello @loom-dev<mailto:[email protected]>,

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

Reply via email to