My experience with Structured Concurrency

David Alayachew davidalayachew at gmail.com
Sun Aug 17 21:00:29 UTC 2025


> I will guess that #1, #2, and #5 are relatively
> simpler Joiner implementations, is there really
> any benefit to use composition or inheritance here?

Sorry, I have been unclear. Let me clarify.

Yes, it is not hard at all to implement #1, #2, and #5 at all. But I don't
have 5 joiners. I have well past 30 of them.

Most of my joiners are very similar to each other. The 5 I showed you are
most of the major "categories" of joiners I would make. But each category
would have many derivatives of it.

For example, #2 had a derivative that was the same, but for multiple
Exception types as opposed to just one. Another derivative of #2 would go
past a type test, and actually look at the fields of the Exception (like
HTTP Error Code). Yet another would go one level deep, in case the
exception was wrapped (like wrapping an HTTP Exception in a
RuntimeException).

So I started making a whole new class each time I wanted to do almost the
same thing. But you start to run out of names that accurately describe what
you are doing. And yeah, some joiners are going to be heavily reused, so it
makes sense for them to have a whole type (and maybe source file). But many
won't either.

Once I realized that I was making a bunch of the same thing with minor
variations, that's when I started thinking about inheritance and
composition. Sorry if I made it sound like that's what I first jumped to.
No, I thought about inheritance and composition because those are usually
the default answers to the question of "How do I do what T is doing, but
with a minor variation?"

But inheritance and composition didn't get me very far for these joiners,
which is what I was trying to say in my original email. Inheritance with
state is error-prone (from my experience). And composition meant that I was
making my code brittle. What if those methods I am depending upon need to
change?

So I went back to making each joiner be its own thing. In reality, most of
my custom Joiners were either a simple record implementing the Joiner, or
an anonymous class. Records are fine, but you start to run out of
reasonable names when you have 5 different records that do close to the
same thing. I kind of found a compromise by creating the record Joiner
inside the method itself that I am working in (or the class if other
methods need it too). That way, it's scoped off from the rest of the world.
But considering how many I was making, it felt like a clunky solution. I'm
fine peppering my code base with inlined records all over the place *as
long as those records don't have a body*. But once they do, it starts to
get annoying, and makes the code harder to read and skim.

>From there, I thought about making a factory. You know the rest of the
story.

> For #4 and #5 then its surprising that there is RPC
> or split/join in the onComplete method.  The
> onComplete method is called with the completed
> subtask and any exception/error executing
> onComplete isn't going to change the subtask
> status. Is there a reason you've chosen to put
> that code there rather than in the subtasks?

Yeah. Long story short, if that RPC call or the nested scope fails, well
the literal goal that I created this scope to do (contruct an object) has,
in effect, failed. That's grounds to just throw an exception and see if
someone upstream can handle it. Maybe a retry or something.

To me, it felt like I was keeping inline with what onComplete was trying to
do -- sort of be an AOP-like post-processing joinpoint. If the contents of
onComplete fails, well then the goal was unattainable anyways, so killing
the scope via thrown exception doesn't feel wrong. And the exception will
propagate, so it felt like I was following right along with how the spec
intended things to go. Granted, I certainly am marching on the edge here,
I'll concede that.

> (for his API then the question as to "where" to
> put code is a good discussion as it may not be
> always obvious whether to code should execute in
> the subtask, in the Joiner handling subtask
> completion, or in the main task in the processing
> after join.

I would love a short guide on what code to put in what place. This looks to
be a pretty integral API for handling a large number of tasks moving
forward, so I see value in it.


On Sun, Aug 17, 2025 at 1:13 PM Alan Bateman <alan.bateman at oracle.com>
wrote:

> On 16/08/2025 20:23, David Alayachew wrote:
>
> :
>
> Sure. Let me highlight 5 of them. Let me know if you need more examples --
> I have about 30+ custom implementations.
>
>
> Thanks for sharing this selection.
>
> I will guess that #1, #2, and #5 are relatively simpler Joiner
> implementations, is there really any benefit to use composition or
> inheritance here?
>
> For #4 and #5 then its surprising that there is RPC or split/join in the
> onComplete method.  The onComplete method is called with the completed
> subtask and any exception/error executing onComplete isn't going to change
> the subtask status. Is there a reason you've chosen to put that code there
> rather than in the subtasks? (for his API then the question as to "where"
> to put code is a good discussion as it may not be always obvious whether to
> code should execute in the subtask, in the Joiner handling subtask
> completion, or in the main task in the processing after join.
>
> -Alan
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250817/ad5b7e8e/attachment.htm>


More information about the loom-dev mailing list