<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"Jige Yu" <yujige@gmail.com><br><b>To: </b>"loom-dev" <loom-dev@openjdk.org><br><b>Sent: </b>Sunday, October 12, 2025 7:32:33 AM<br><b>Subject: </b>Feedback on Structured Concurrency (JEP 525, 6th Preview)<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><h3><span style="font-size:small;font-weight:normal">Hi Project Loom.</span></h3><h3><span style="font-size:small;font-weight:normal">First and foremost, I want to express my gratitude for the effort that has gone into structured concurrency. API design in this space is notoriously difficult, and this feedback is offered with the greatest respect for the team's work and in the spirit of collaborative refinement.</span></h3><p>My perspective is that of a developer looking to use Structured Concurrency for common, IO-intensive fan-out operations. My focus is to replace everyday <font face="monospace">async callback hell,</font> or reactive chains with something simpler and more readable.</p><p>It will lack depth in the highly specialized concurrent programming area. And I acknowledge this viewpoint may bias my feedback.</p><hr></div></blockquote><div>[...]</div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><h2><b>Suggestions for a Simpler Model</b></h2><p>My preference is that the API for the most common use cases should be more <b>declarative and functional</b>.</p><ol start="1"><li><p><b>Simplify the "Gather All" Pattern:</b> The primary "fan-out and gather" use case could be captured in a simple, high-level construct. An average user shouldn't need to learn the wide API surface of StructuredTaskScope + Joiner + the lifecycles. For example:</p><span class="gmail-"><span class="gmail-ng-tns-c3565927614-183 gmail-ng-star-inserted"><div class="gmail-code-block gmail-ng-tns-c3565927614-183 gmail-ng-animate-disabled gmail-ng-trigger gmail-ng-trigger-codeBlockRevealAnimation"><div class="gmail-code-block-decoration gmail-header-formatted gmail-gds-title-s gmail-ng-tns-c3565927614-183 gmail-ng-star-inserted"><span class="gmail-ng-tns-c3565927614-183">Java</span></div><div class="gmail-formatted-code-block-internal-container gmail-ng-tns-c3565927614-183"><div class="gmail-animated-opacity gmail-ng-tns-c3565927614-183"><pre class="gmail-ng-tns-c3565927614-183"><code class="gmail-code-container gmail-formatted gmail-ng-tns-c3565927614-183"><span class="gmail-hljs-comment">// Ideal API for the 80% use case</span>
Robot robot = Concurrently.call(
    () -> fetchArm(),
    () -> fetchLeg(),
    (arm, leg) -> <span class="gmail-hljs-keyword">new</span> Robot(arm, leg)
);</code></pre></div></div></div></span></span></li></ol></div></blockquote><div><br></div><div>I'm curious how you want to type that API, does it work only for two tasks, do you have an overload for each arity (2 tasks, 3 tasks, etc).</div><div>And how exceptions are supposed to work given that the type system of Java is not able to merge type variable representing exceptions correctly. </div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><ol start="1"><li><p><b>Separate Race Semantics into Composable Operations:</b> The "race" pattern feels like a distinct use case that could be implemented more naturally using composable, functional APIs like Stream gatherers, rather than requiring a specialized API at all. For example, if <code>mapConcurrent()</code> fully embraced structured concurrency, guaranteeing fail-fast and happens-before, a recoverable race could be written explicitly:</p><span class="gmail-"><span class="gmail-ng-tns-c3565927614-184 gmail-ng-star-inserted"><div class="gmail-code-block gmail-ng-tns-c3565927614-184 gmail-ng-animate-disabled gmail-ng-trigger gmail-ng-trigger-codeBlockRevealAnimation"><div class="gmail-code-block-decoration gmail-header-formatted gmail-gds-title-s gmail-ng-tns-c3565927614-184 gmail-ng-star-inserted"><span class="gmail-ng-tns-c3565927614-184">Java</span></div><div class="gmail-formatted-code-block-internal-container gmail-ng-tns-c3565927614-184"><div class="gmail-animated-opacity gmail-ng-tns-c3565927614-184"><pre class="gmail-ng-tns-c3565927614-184"><code class="gmail-code-container gmail-formatted gmail-ng-tns-c3565927614-184"><span class="gmail-hljs-comment">// Pseudo-code for a recoverable race using a stream gatherer</span>
<T> <span class="gmail-hljs-function">T <span class="gmail-hljs-title">race</span><span class="gmail-hljs-params">(Collection<Callable<T>> tasks, <span class="gmail-hljs-keyword">int</span> maxConcurrency)</span></span>{
    <span class="gmail-hljs-keyword">var</span> exceptions = <span class="gmail-hljs-keyword">new</span> ConcurrentLinkedQueue<RpcException>();
    <span class="gmail-hljs-keyword">return</span> tasks.stream()
        .gather(mapConcurrent(maxConcurrency, task -> {
            <span class="gmail-hljs-keyword">try</span> {
                <span class="gmail-hljs-keyword">return</span> task.call();
            } <span class="gmail-hljs-keyword">catch</span> (RpcException e) {
                <span class="gmail-hljs-keyword">if</span> (isRecoverable(e)) { <span class="gmail-hljs-comment">// Selectively recover</span>
                    exceptions.add(e);
                    <span class="gmail-hljs-keyword">return</span> <span class="gmail-hljs-keyword">null</span>; <span class="gmail-hljs-comment">// Suppress and continue</span>
                }
                <span class="gmail-hljs-keyword">throw</span> <span class="gmail-hljs-keyword">new</span> RuntimeException(e); <span class="gmail-hljs-comment">// Fail fast on non-recoverable</span>
            }
        }))
        .filter(Objects::nonNull)
        .findFirst() <span class="gmail-hljs-comment">// Short-circuiting and cancellation</span>
        .orElseThrow(() -> <span class="gmail-hljs-keyword">new</span> AggregateException(exceptions));
}
</code></pre></div></div></div></span></span><p>While this is slightly more verbose than the JEP example, it's familiar Stream semantics that people have already learned, and it offers explicit control over which exceptions are recoverable versus fatal. The boilerplate for exception aggregation could easily be wrapped in a helper method.</p></li></ol></div></blockquote><div><br></div><div>Several points :</div><div>- I believe the current STS API has no way to deal with if the exception is recoverable or not because it's far easier to do that at the end of the callable.</div><div>  Your example becomes : </div><div>    </div><div>    sts.fork(() -> {</div><div>      try { </div><div>        taskCall();</div><div>      } catch(RPCException e) {</div><div>        ...</div><div>      }</div><div>    });</div><div><br data-mce-bogus="1"></div><div>- You do not want to post the result/exception of a task into a concurrent data structure, i think the idea of the STS API in this case is to fork all the tasks and then take a look to all the subtasks.</div><div>  I believe it's more efficient because there is no CAS to be done if the main thread take a look to the subtasks afterward than if the joiner tries to maintain a concurrent data structure.</div><div><br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><ol start="1"><li><p><b>Reserve Complexity for Complex Cases:</b> The low-level <code>StructuredTaskScope</code> and its policy mechanism are powerful tools. However, they should be positioned as the "expert-level" API for building custom frameworks. Or perhaps just keep them in the traditional ExecutorService API. The everyday developer experience should be centered around simpler, declarative constructs that cover the most frequent needs.</p></li></ol></div></blockquote><div><br></div><div>For me, that's why you have an open Joiner interface for expert and already available Joiner (like all.../any...) that are more for everyday developers.</div><div><br data-mce-bogus="1"></div><div>regards,</div><div>RĂ©mi</div><div><br data-mce-bogus="1"></div><div><br></div></div></div></body></html>