> From: "Robert Engels" <[email protected]> > To: "Remi Forax" <[email protected]> > Cc: "Jige Yu" <[email protected]>, "loom-dev" <[email protected]> > Sent: Sunday, October 12, 2025 3:52:02 PM > Subject: Re: Feedback on Structured Concurrency (JEP 525, 6th Preview)
> Checked exceptions are great. Just need to understand how to use them > properly. > I think the Swift way of addressing it is fantastic. see [ https://www.artima.com/articles/the-trouble-with-checked-exceptions | https://www.artima.com/articles/the-trouble-with-checked-exceptions ] regards, Rémi >> On Oct 12, 2025, at 8:28 AM, Remi Forax <[email protected]> wrote: >>> From: "Jige Yu" <[email protected]> >>> To: "loom-dev" <[email protected]> >>> Sent: Sunday, October 12, 2025 7:32:33 AM >>> Subject: Feedback on Structured Concurrency (JEP 525, 6th Preview) >>> Hi Project Loom. >> Hello, >>> 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. >>> 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 >>> async >>> callback hell, or reactive chains with something simpler and more readable. >>> It will lack depth in the highly specialized concurrent programming area. >>> And I >>> acknowledge this viewpoint may bias my feedback. >>> High-Level Impression >>> From this perspective, the current API feels imperative and more complex >>> for the >>> common intended use cases than necessary. It introduces significant >>> cognitive >>> load through its stateful nature and manual lifecycle management. >>> Specific Points of Concern >>> 1. >>> Stateful and Imperative API: The API imposes quite some "don't do this at >>> time >>> X" rules. Attempting to fork() after join() leads to a runtime error; >>> forgetting to call join() is another error; and the imperative fork / join >>> sequence is more cumbersome than a declarative approach would be. None of >>> these >>> are unmanageable though. >> I had a similar feeling the first time I used the API, but once you play with >> it, it kind of make sense. >> The API can be used when all tasks are different (concurrent tasks) or when >> all >> task are the same (parallel tasks), a more functional API will only work with >> the latter. >>> 1. >>> Challenging Exception Handling: The exception handling model is tricky: >>> * >>> Loss of Checked Exception Compile-Time Safety: FailedException is >>> effectively an >>> unchecked wrapper that erases checked exception information at compile time. >>> Migrating from sequential, structured code to concurrent code now means >>> losing >>> valuable compiler guarantees. >> You can propagate the exceptions but it makes the API clunkier (one more type >> variable everywhere) and do not solve the fundamental problem that you do not >> want to merge the control flow of an exception that comes from a callable >> with >> one that comes from STS.join(). By example, distinguishing if an >> InterruptedException is raised because the main thread is interrupted or if >> one >> of the callable is interrupted (and this is the same will all runtime >> exceptions). >>> 1. >>> * >>> No Help For Exception Handling: For code that wants to catch and handle >>> these >>> exceptions, it's the same story of using instanceof on the getCause(), >>> again, >>> losing all compile-time safety that was available in equivalent sequential >>> code. >> see above >>> 1. >>> * >>> Burdensome InterruptedException Handling: The requirement for the caller to >>> handle or propagate InterruptedException from join() will add room for >>> error as >>> handling InterruptedException is easy to get wrong: one can forget to call >>> currentThread().interrupt(). Or, if the caller decides to declare throws >>> InterruptedException , the signature propagation becomes viral. >> Having InterruptedException not being runtime exception is a pain. But this >> is a >> pain for all blocking methods. >> And BTW, you can also wrap it into a runtime exception (usually >> UncheckedIOException/IOError) which works better than >> currentThread().interrupt() because you do not loose the context (the stack >> trace) and avoid the problem of the signature propagation. >> Perhaps at some point in the future, all exceptions will be runtime >> exceptions >> (like in Kotlin or C#) but this is a Java problem not a problem of the STS >> API. >>> 1. >>> * >>> Default Exception Swallowing: The AnySuccessOrThrow policy swallows all >>> exceptions by default, including critical ones like NullPointerException , >>> IllegalArgumentException , or even an Error . This makes it dangerously >>> easy to >>> mask bugs that should be highly visible. There is no straightforward >>> mechanism >>> to inspect these suppressed exceptions or fail on specific, unexpected >>> types. >> The straightforward mechanism is to inspect the Subtasks that keep that >> information (if available). >>> 1. >>> Conflated API Semantics: The StructuredTaskScope API unifies two very >>> different >>> concurrency patterns—"gather all" ( allSuccessfulOrThrow ) and "race to >>> first >>> success" ( anySuccessfulResultOrThrow )—under a single class but with >>> different >>> interaction models for the same method. >>> * >>> In the "gather all" pattern ( allSuccessfulOrThrow ), join() returns void . >>> The >>> callsite should use subtask.get() to retrieve results. >>> * >>> In the "race" pattern ( anySuccessfulResultOrThrow ), join() returns the >>> result >>> ( R ) of the first successful subtask directly. The developer should not >>> call >>> get() on individual subtasks. Having the join()+subtask.get() method spec'ed >>> conditionally (which method to use and how depends on the actual policy) >>> feels >>> like a minor violation of LSP and is a source of confusion. It may be an >>> indication of premature abstraction. >> I kind agree on this one, i.e. i would like the semantics of when to stop the >> STS and the semantics of getting all subtaks or not to be separated given >> there >> are separated concern. >>> 1. >>> Overly Complex Customization: The StructuredTaskScope.Policy API, while >>> powerful, feels like a potential footgun. The powerful lifecycle callback >>> methods like onFork(), onComplete(), onTimeout() may lower the barrier to >>> creating intricate, framework-like abstractions that are difficult to reason >>> about and debug. >> yes, especially if you try to do reduce to a value (like a Collector) inside >> the >> Joiner but this is called out by the documentation. >> I will answer the rest of the mail, in a new message. >> Rémi
